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

后端 未结 4 1297
再見小時候
再見小時候 2020-12-19 12:53

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<100         


        
相关标签:
4条回答
  • 2020-12-19 13:28

    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.

    0 讨论(0)
  • 2020-12-19 13:38

    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.

    0 讨论(0)
  • 2020-12-19 13:53

    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.

    0 讨论(0)
  • 2020-12-19 13:54

    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.

    0 讨论(0)
提交回复
热议问题