Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

pthreads: If I increment a global from two different threads, can there be sync issues?

Suppose I have two threads A and B that are both incrementing a ~global~ variable "count". Each thread runs a for loop like this one:

for(int i=0; i<1000; i++)
    count++; //alternatively, count = count + 1;

i.e. each thread increments count 1000 times, and let's say count starts at 0. Can there be sync issues in this case? Or will count correctly equal 2000 when the execution is finished? I guess since the statement "count = count + 1" may break down into TWO assembly instructions, there is potential for the other thread to be swapped in between these two instructions? Not sure. What do you think?

like image 543
mindthief Avatar asked Sep 09 '10 01:09

mindthief


4 Answers

Yes there can be sync issues in this case. You need to either protect the count variable with a mutex, or use a (usually platform specific) atomic operation.

Example using pthread mutexes

static pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;

for(int i=0; i<1000; i++) {
    pthread_mutex_lock(&mutex);
    count++;
    pthread_mutex_unlock(&mutex);
}

Using atomic ops

There is a prior discussion of platform specific atomic ops here: UNIX Portable Atomic Operations

If you only need to support GCC, this approach is straightforward. If you're supporting other compilers, you'll probably have to make some per-platform decisions.

like image 179
blucz Avatar answered Sep 23 '22 19:09

blucz


Count clearly needs to be protected with a mutex or other synchronization mechanism.

At a fundamental level, the count++ statment breaks down to:

load count into register
increment register
store count from register

A context switch could occur before/after any of those steps, leading to situations like:

Thread 1:  load count into register A (value = 0)
Thread 2:  load count into register B (value = 0)
Thread 1:  increment register A (value = 1)
Thread 1:  store count from register A (value = 1)
Thread 2:  increment register B (value = 1)
Thread 2:  store count from register B (value = 1)

As you can see, both threads completed one iteration of the loop, but the net result is that count was only incremented once.

You probably would also want to make count volatile to force loads & stores to go to memory, since a good optimizer would likely keep count in a register unless otherwise told.

Also, I would suggest that if this is all the work that's going to be done in your threads, performance will dramatically drop from all the mutex locking/unlocking required to keep it consistent. Threads should have much bigger work units to perform.

like image 33
Drew Hall Avatar answered Sep 23 '22 19:09

Drew Hall


Yes, there can be sync problems.

As an example of the possible issues, there is no guarantee that an increment itself is an atomic operation.

In other words, if one thread reads the value for increment then gets swapped out, the other thread could come in and change it, then the first thread will write back the wrong value:

+-----+
|   0 | Value stored in memory (0).
+-----+
|   0 | Thread 1 reads value into register (r1 = 0).
+-----+
|   0 | Thread 2 reads value into register (r2 = 0).
+-----+
|   1 | Thread 2 increments r2 and writes back.
+-----+
|   1 | Thread 1 increments r1 and writes back.
+-----+

So you can see that, even though both threads have tried to increment the value, it's only increased by one.

This is just one of the possible problems. It may also be that the write itself is not atomic and one thread may update only part of the value before being swapped out.

If you have atomic operations that are guaranteed to work in your implementation, you can use them. Otherwise, use mutexes, That's what pthreads provides for synchronisation (and guarantees will work) so is the safest approach.

like image 37
paxdiablo Avatar answered Sep 24 '22 19:09

paxdiablo


I guess since the statement "count = count + 1" may break down into TWO assembly instructions, there is potential for the other thread to be swapped in between these two instructions? Not sure. What do you think?

Don't think like this. You're writing C code and pthreads code. You don't have to ever think about assembly code to know how your code will behave.

The pthreads standard does not define the behavior when one thread accesses an object while another thread is, or might be, modifying it. So unless you're writing platform-specific code, you should assume this code can do anything -- even crash.

The obvious pthreads fix is to use mutexes. If your platform has atomic operations, you can use those.

I strongly urge you not to delve into detailed discussions about how it might fail or what the assembly code might look like. Regardless of what you might or might not think compilers or CPUs might do, the behavior of the code is undefined. And it's too easy to convince yourself you've covered every way you can think of that it might fail and then you miss one and it fails.

like image 25
David Schwartz Avatar answered Sep 25 '22 19:09

David Schwartz