Why volatile and MemoryBarrier do not prevent operations reordering?

后端 未结 4 1247
耶瑟儿~
耶瑟儿~ 2021-02-05 17:10

If I understand meaning of volatile and MemoryBarrier correctly than the program below has never to be able to show any result.

It catches reordering of write operations

相关标签:
4条回答
  • 2021-02-05 17:38

    You aren't cleaning the variables between tests, so (for all but the first) initially a is 2 and b is 20 - before Write has done anything.

    Check can get that initial value of a (so tempA is 2), and then Write can get in, get as far as changing b to 10.

    Now Check reads the b (so tempB is 10).

    Et voila. No re-order necessary to repro.

    Reset a and b to 0 between runs and I expect it will go away.

    edit: confirmed; "as is" I get the issue almost immediately (<2000 iterations); but by adding:

    while (continueTrying)
    {
        a = b = 0; // reset <======= added this
    

    it then loops for any amount of time without any issue.

    Or as a flow:

    Write                   A=  B=        Check
    
    (except first run)      2   20
                                          int tempA = a;
    a = 1;                  1   20
    Thread.MemoryBarrier();
    b = 10;                 1   10
                                          int tempB = b;
    
    0 讨论(0)
  • 2021-02-05 17:40
    1. If you use MemoryBarrier in writer, why don't you do that in checker? Put Thread.MemoryBarrier(); before int tempA = a;.

    2. Calling Thread.MemoryBarrier(); so many times blocks all of the advantages of the method. Call it only once before or after a = 1;.

    0 讨论(0)
  • 2021-02-05 17:44

    I don't think this is re-ordering.

    This piece of code is simply not thread-safe:

     while (continueChecking)
     {
         int tempA = a;
         int tempB = b;
         ...
    

    I think this scenario is possible:

    1. int tempA = a; executes with the values of the last loop (a == 2)
    2. There is a context switch to the Write thread
    3. b = 10 and the loop stops
    4. There is a context switch to the Check thread
    5. int tempB = b; executes with b == 10

    I notice that the calls to MemoryBarrier() enhance the chances of this scenario. Probably because they cause more context-switching.

    0 讨论(0)
  • 2021-02-05 17:44

    The result has nothing to do with reordering, with memory barries, or with volatile. All these constructs are needed to avoid effects of compiler or CPU reordering of the instructions.

    But this program would produce the same result even assuming fully consistent single-CPU memory model and no compiler optimization.

    First of all, notice that there will be multiple Write() tasks started in parallel. They are running sequentially due to lock() inside Write(), but a signle Check() method can read a and b produced by different instances of Write() tasks.

    Because Check() function has no synchronization with Write function - it can read a and b at two arbitrary and different moments. There is nothing in your code that prevents Check() from reading a produced by previous Write() at one moment and then reading b produced by following Write() at another moment. First of all you need synchronization (lock) in Check() and then you might (but probably not in this case) need memory barriers and volatile to fight with memory model problems.

    This is all you need:

            int tempA, tempB;
            lock (locker)
            {
                tempA = a;
                tempB = b;
            }
    
    0 讨论(0)
提交回复
热议问题