Search code examples
c#multithreadingvolatilememory-barriersmemory-fences

Do memory barriers guarantee a fresh read in C#?


If we have the following code in C#:

int a = 0;
int b = 0;

void A() // runs in thread A
{
    a = 1;
    Thread.MemoryBarrier();
    Console.WriteLine(b);
}

void B() // runs in thread B
{
    b = 1;
    Thread.MemoryBarrier();
    Console.WriteLine(a);
}

The MemoryBarriers make sure that the write instruction takes place before the read. However, is it guaranteed that the write of one thread is seen by the read on the other thread? In other words, is it guaranteed that at least one thread prints 1 or both thread could print 0?

I know that several questions exist already that are relevant to "freshness" and MemoryBarrier in C#, like this and this. However, most of them deal with the write-release and read-acquire pattern. The code posted in this question, is very specific to whether a write is guaranteed to be viewed by a read on top of the fact that the instructions are kept in order.


Solution

  • It is not guaranteed to see both threads write 1. It only guarantees the order of read/write operations based on this rule:

    The processor executing the current thread cannot reorder instructions in such a way that memory accesses prior to the call to MemoryBarrier execute after memory accesses that follow the call to MemoryBarrier.

    So this basically means that the thread for a thread A wouldn't use a value for the variable b read before the barrier's call. But it still cache the value if your code is something like this:

    void A() // runs in thread A
    {
        a = 1;
        Thread.MemoryBarrier();
        // b may be cached here
        // some work here
        // b is changed by other thread
        // old value of b is being written
        Console.WriteLine(b);
    }
    

    The race-condition bugs for a the parallel execution is very hard to reproduce, so I can't provide you a code that will definitely do the scenario above, but I suggest you to use the volatile keyword for the variables being used by different threads, as it works exactly as you want - gives you a fresh read for a variable:

    volatile int a = 0;
    volatile int b = 0;
    
    void A() // runs in thread A
    {
        a = 1;
        Thread.MemoryBarrier();
        Console.WriteLine(b);
    }
    
    void B() // runs in thread B
    {
        b = 1;
        Thread.MemoryBarrier();
        Console.WriteLine(a);
    }