none
Can anyone give me an example to prove "volitale" and "Thread.MemoryBarrier" and lock? RRS feed

  • Question

  • I was told by someone that "volitale" is of "half-fence" but the later is of full-fence……still don't understand at all……Anyone must be with an example and tell me why……???
    Friday, August 24, 2012 1:58 AM

Answers

  • One relatively common use of volatile is when you have a thread that needs to do some work repeatedly until it is "told" to exit:

    Thread1: while (!exit) { // do some work }             Thread2: exit = true; // tell Thread1 to exit

    If the exit variable is not marked volatile then there is a possibility that this code won't work properly. For example the compiler may see that exit never changes (it has no way of knowing about Thread2) and it will load 'exit' from memory once and then reuse it over and over again. The result is that Thread1 will never exit the loop even if exit is set to true.

    But this doesn't have much to do with reordering memory accesses and fences, for this you usually need more than one variable. Like you may have in some kind of "one-time initialization" code:

    bool initialized;
    int value;
    
    int GetValue() {
        if (!initialized) {
            value = 42;
            initialized = true;
        }
        return value;
    }
    
    

    This code works fine if a single thread calls GetValue but then multiple threads are involved then the correctness of this code depends on the order in which 'value' and 'initialized' are set. For example it is possible that due to compiler or CPU optimizations 'initialized' is set to true before 'value' is set to 42. Another thread sees that 'initialized' is true and simply returns 'value', but 'value' hasn't been set yet.

    How to fix this? By using fences of course. Let's see what happens if we make 'initialized' volatile:

    • we get an "acquire fence" for the read in "if (!initialized)". This means that there's no way for a read of 'value' to move before "if (!initialized)". Good, we should never read the value before checking if it's initialized.
    • we get a "release fence" for "initialized = true;". This means that "value = 42;" cannot be moved before "initialized = true;" anymore. Good again, the value must always be set before setting initialized to true.

    I don't have a practical example for MemoryBarrier (full fence) but it would be something along these lines:

     
    volatile int a;
    volatile int b;
    void SomeCode() {
        a = 42; // a release fence
        if (b > 42) { // an acquire fence
            ...
        }
    }
    

    Basically a release fence followed by an acquire fence. These fences have no effect in this case, 'release' is supposed to prevent moving something after it but there's nothing before it that can be moved. 'Acquire' is suppose to prevent moving something before and this doesn't affect 'a = 42'. So nothing stops reordering here. If for some reason you must be sure a is written before b is read then you must put a MemoryBarrier between "a = 42;" and "if (b > 42)".
    • Marked as answer by TimoYang Wednesday, August 29, 2012 4:54 AM
    • Unmarked as answer by TimoYang Wednesday, August 29, 2012 4:58 AM
    • Marked as answer by TimoYang Tuesday, September 4, 2012 5:05 AM
    Tuesday, August 28, 2012 4:27 PM
    Moderator
  • I just modify Mike’s code as below, however, below code has same behavior with Mike’s

        class Program

        {

            static bool exit = false;

            static void Main(string[] args)

            {

                //bool exit = false;

                Thread th1 = new Thread(M1);

                Thread th2 = new Thread(M2);

                th1.Start();

                th2.Start();

            }

            static void M1()

            {

                inta = 0;

                while(!exit) { a++; }

                Console.WriteLine(a);

            }

            static void M2()

            {

                Thread.Sleep(100);

                exit = true;

            }

        }

    I run the test code, and you can find the jitted instructions of ConsoleApplication1.Program.M1() method below, you have different processor, JIT may generate different code for you.

    ConsoleApplication1!ConsoleApplication1.Program.M1()

    00300133 56              push    esi

    00300134 33f6            xor     esi,esi

    00300136 0fb6058a320d00  movzx   eax,byte ptr ds:[0D328Ah]  [Eric: this address 0x0D328Ah refer to the global variable 'exit', when M2() execute this instruction, the value (at address 0x0D328Ah) is 0, so eax is set to 0, the subsequence call uses the cached value 0 from eax]

    0030013d 85c0            test    eax,eax [Eric: eax is 0 now, enter infinit loop... //start loop]

    0030013f 7505            jne     ConsoleApplication1!ConsoleApplication1.Program.M1()+0x16 (00300146)

    00300141 46              inc     esi

    00300142 85c0            test    eax,eax

    00300144 74fb            je      ConsoleApplication1!ConsoleApplication1.Program.M1()+0x11 (00300141) [Eric: //end loop]

    00300146 e879a37f6e      call    mscorlib_ni!System.Console.get_Out() (6eafa4c4)

    0030014b 8bc8            mov     ecx,eax

    0030014d 8bd6            mov     edx,esi

    0030014f 8b01            mov     eax,dword ptr [ecx]

    00300151 8b4038          mov     eax,dword ptr [eax+38h]

    00300154 ff5014          call    dword ptr [eax+14h]

    00300157 5e              pop     esi

    00300158 5d              pop     ebp

    00300159 c3              ret

    0:004> r

    eax=00000000 ebx=02682334 ecx=026822cc edx=026822cc esi=f08c745c edi=04c5f1fc

    eip=00300142 esp=04c5f1ac ebp=04c5f1b0 iopl=0         nv up ei ng nz na pe nc

    cs=0023  ss=002b  ds=002b  es=002b  fs=0053  gs=002b             efl=00000286

    ConsoleApplication1!ConsoleApplication1.Program.M1()+0x12:

    00300142 85c0            test    eax,eax


    Please remember to click “Mark as Answer” on the post that helps you, and to click “Unmark as Answer” if a marked post does not actually answer your question. This can be beneficial to other community members reading the thread.

    Regards,
    Eric Yang
    Microsoft Online Community Support



    • Edited by Eric Ya - MSFT Monday, September 3, 2012 5:47 AM
    • Marked as answer by TimoYang Tuesday, September 4, 2012 5:05 AM
    Thursday, August 30, 2012 12:14 PM

All replies

  • "volatile" is not a thread safe synchronization object. It is unsafe when used on machines with more than one CPU's. Two threads running on 2 separate CPU's may interleave their read and write operation on the volatile variables.

    A better alternative is "lock". This works across CPU's. But the performance for lock is not so good.

    The best way to synchronize threads according to me is to use "Interlocked.Increment()". This is the fastest and safest.

    Friday, August 24, 2012 5:39 AM
  • I know, but do u mean I'm using Intel (I5——Two cores)……Can u give me an example?!

    Plz prove the MOST DIFFERENCE between volitale/Thread.memoryBarrier/Thread.VolitaleRead/VolitaleWrite!

    Thxxxxxxxxxx

    • Edited by TimoYang Sunday, August 26, 2012 3:34 AM
    Sunday, August 26, 2012 3:32 AM
  • volatile, Thread.VolatileRead, VolitaleWrite ensure the atomic operation, i mean, the data is not read/written partially.

    Thread.MemoryBarrier instructs compiler not to re-align the code during optimization.

    I think, if you are using latest .NET framework, then you need not worry about these things as the compiler has grown smart enough to handle these things automatically for you.

    I hope this helps.


    Please mark this post as answer if it solved your problem. Happy Programming!

    Sunday, August 26, 2012 5:30 AM
  • A "fence" prevents reordering of memory operations (loads from memory and stores to memory). By "half-fence" he probably means acquire/release fence which is what volatile does. To be precise:

    • reading a volatile field implies an "acquire fence". This means that no memory operations after the fence can be moved before it.
    • writing to a volatile field implies a "release fence". This means that no memory operations before the fence can be moved after it.

    Now "full fence" should be obvious: No memory operations can moves before or after it. VolatileRead/VolatileWrite and currently implemented by using MemoryBarrier so they're full fences too.

    Of course, there's a bit more to volatile than just these fences, volatile also prevents the compiler from performing some optimizations. In particular it forces it to always read the volatile field instead of reusing a value that may have been previously loaded into a register.

    Also:

    • Volatile can sometimes be used instead of a lock but it works very differently. By itself volatile is not a lock and it cannot be used to implement one.
    • Volatile does not ensure atomicity. Volatile only applies to read/writes from/to memory. These are already atomic for some types (int, float, reference on x86) and not atomic for others (long, double on x86). You can't even make a long or double field volatile.
    • All writes, not only volatile ones, use release fences in current versions of .NET running on x86.
    • For the sake of completeness I'll add that locks and interlocked operations act as full fences.
    Sunday, August 26, 2012 7:06 AM
    Moderator
  • Mike, plz offer me an example……plz……:)
    Tuesday, August 28, 2012 5:00 AM
  • Hi Timo,

    I am trying to involve some other one in this thread, please wait it patiently.

    Our  managed forum is focused on break fix issues that are neither urgent, nor complex. If the issue is urgent to your business, it is recommended that you contact Microsoft Customer Spport Services(CSS) via telephone so that a dedicated Support Professional can assist you in a more efficient manner. Please be advised that contacting phone support will be a charged call. 

    to obtain the phone numbers for specific technology request please take a look at the web site listed below.

    http://support.microsoft.com/default.aspx?scid=fh;EN-US;PHONENUMBERS  

    If you are outside the US please see http://support.microsoft.com for regional support phone numbers.

    Best regards,


    Mike Feng
    MSDN Community Support | Feedback to us
    Please remember to mark the replies as answers if they help and unmark them if they provide no help.


    Tuesday, August 28, 2012 11:08 AM
    Moderator
  • One relatively common use of volatile is when you have a thread that needs to do some work repeatedly until it is "told" to exit:

    Thread1: while (!exit) { // do some work }             Thread2: exit = true; // tell Thread1 to exit

    If the exit variable is not marked volatile then there is a possibility that this code won't work properly. For example the compiler may see that exit never changes (it has no way of knowing about Thread2) and it will load 'exit' from memory once and then reuse it over and over again. The result is that Thread1 will never exit the loop even if exit is set to true.

    But this doesn't have much to do with reordering memory accesses and fences, for this you usually need more than one variable. Like you may have in some kind of "one-time initialization" code:

    bool initialized;
    int value;
    
    int GetValue() {
        if (!initialized) {
            value = 42;
            initialized = true;
        }
        return value;
    }
    
    

    This code works fine if a single thread calls GetValue but then multiple threads are involved then the correctness of this code depends on the order in which 'value' and 'initialized' are set. For example it is possible that due to compiler or CPU optimizations 'initialized' is set to true before 'value' is set to 42. Another thread sees that 'initialized' is true and simply returns 'value', but 'value' hasn't been set yet.

    How to fix this? By using fences of course. Let's see what happens if we make 'initialized' volatile:

    • we get an "acquire fence" for the read in "if (!initialized)". This means that there's no way for a read of 'value' to move before "if (!initialized)". Good, we should never read the value before checking if it's initialized.
    • we get a "release fence" for "initialized = true;". This means that "value = 42;" cannot be moved before "initialized = true;" anymore. Good again, the value must always be set before setting initialized to true.

    I don't have a practical example for MemoryBarrier (full fence) but it would be something along these lines:

     
    volatile int a;
    volatile int b;
    void SomeCode() {
        a = 42; // a release fence
        if (b > 42) { // an acquire fence
            ...
        }
    }
    

    Basically a release fence followed by an acquire fence. These fences have no effect in this case, 'release' is supposed to prevent moving something after it but there's nothing before it that can be moved. 'Acquire' is suppose to prevent moving something before and this doesn't affect 'a = 42'. So nothing stops reordering here. If for some reason you must be sure a is written before b is read then you must put a MemoryBarrier between "a = 42;" and "if (b > 42)".
    • Marked as answer by TimoYang Wednesday, August 29, 2012 4:54 AM
    • Unmarked as answer by TimoYang Wednesday, August 29, 2012 4:58 AM
    • Marked as answer by TimoYang Tuesday, September 4, 2012 5:05 AM
    Tuesday, August 28, 2012 4:27 PM
    Moderator
  • Hi Timo,

    I am trying to involve some other one in this thread, please wait it patiently.


    Sorry……:(
    Wednesday, August 29, 2012 4:53 AM
  • Hi Mike Dans:)

    first greate contributors to u!!!

    Then I have another question to ur sample——

    >>One relatively common use of volatile is when you have a thread that needs to do some work repeatedly until it is "told" to exit:
    Thread1: while (!exit) { // do some work } Thread2: exit = true; // tell Thread1 to exit

    According to ur statements,I did this following:

    class Program { static void Main(string[] args) { bool exit = false; Thread th1 = new Thread(() => { while (!exit) { System.Console.WriteLine("Outputting……"); } }); Thread th2 = new Thread(() => { exit = true; });

    th1.Start(); th2.Start();

    } }

    And th1 will be automatically stopped!why?

    Can u give another example to prove this?

    PS:Mike Feng, ur example, plz……

    • Edited by TimoYang Wednesday, August 29, 2012 5:07 AM
    Wednesday, August 29, 2012 5:02 AM
  • As I said previously, this kind of volatile use has more to do with compiler optimizations. It turns out that with your code no 'exit' related optimizations are performed anyway so the effects of volatile can't be seen here. But if we simplify thread1 code like this:

    int a = 0;
    while (!exit) { a++; }
    Console.WriteLine(a);
    

    then the program won't work properly anymore. In debug builds it will work, the while loop will exit and a number will be printed as expected. But in release builds more often than not the loop will never exit, you can wait as long as you want and no number will be printed in console. Occasionally it will exit and print 0, this happens when the second thread has a chance to run before the first thread. In such a case the loop is never entered and that's why the result is 0.

    In general, the optimizations that volatile, MemoryBarrier etc. are supposed to protect against sometimes happen, sometimes don't.

    Wednesday, August 29, 2012 6:05 AM
    Moderator
  • Hi Mike Danes:)

    Sorry but it still only gives me 0 and the result is the same……:( Even if I've changed to Release mode……!

    Mike Feng, are you ready???

    Thursday, August 30, 2012 4:19 AM
  • If you keep getting 0 it means that the second thread keeps starting before thread1. Try adding a Thread.Sleep(100) before "exit = true;" to give the first thread time to enter the while loop.
    Thursday, August 30, 2012 4:40 AM
    Moderator
  • If you keep getting 0 it means that the second thread keeps starting before thread1. Try adding a Thread.Sleep(100) before "exit = true;" to give the first thread time to enter the while loop.

    Sorry Danes:)

    But even if I've added Thread.Sleep(1000) it still loops well……and after a period of time it automatically exits……

    Have u tried before telling me?Anything special?

    Thursday, August 30, 2012 5:25 AM
  • "Have u tried before telling me"

    Yep, it works fine here, that is, it never stops.

    "Anything special"

    Not sure. What version of Visual Studio are you using? Are you running the program from the debugger?

    Thursday, August 30, 2012 5:45 AM
    Moderator
  • Mike Feng, are you ready???

    Hi TimoYang,

    Not yet. If it is very urgent, please contact CSS as I mentioned above.

    Best regards,


    Mike Feng
    MSDN Community Support | Feedback to us
    Please remember to mark the replies as answers if they help and unmark them if they provide no help.

    Thursday, August 30, 2012 6:13 AM
    Moderator
  • "Have u tried before telling me"

    Yep, it works fine here, that is, it never stops.

    "Anything special"

    Not sure. What version of Visual Studio are you using? Are you running the program from the debugger?

    2012, net framework4.5

    give me ur codes, plzzzzzzzzzzzzz

    Thx

    Thursday, August 30, 2012 6:22 AM
  • Mike Feng,

    When ur are ready, plz don't forget to reply to me:D

    Thursday, August 30, 2012 6:23 AM
  • Mike Feng,

    When ur are ready, plz don't forget to reply to me:D

    Hi Timo,

    Yes, I will.

    Best regards,


    Mike Feng
    MSDN Community Support | Feedback to us
    Please remember to mark the replies as answers if they help and unmark them if they provide no help.

    Thursday, August 30, 2012 6:32 AM
    Moderator
  • Well, you already have my code but here it is in full to avoid any errors:

        class Program
        {
            static void Main(string[] args)
            {
                bool exit = false;
                Thread th1 = new Thread(() =>
                {
                    int a = 0;
                    while (!exit) { a++; }
                    Console.WriteLine(a);
                });
                Thread th2 = new Thread(() =>
                {
                    Thread.Sleep(100);
                    exit = true;
                });
    
                th1.Start();
                th2.Start();
            }
        }
    This code doesn't stop and doesn't print anyhting (in the Release build, run outside debugger).
    Thursday, August 30, 2012 6:33 AM
    Moderator
  • Hi Danes:)

    1)Why I should put a+=1 or a++ instead of using Console.WriteLine to make the effect?

    2)Why my codes don't work?

    class Program
        {
            static void Main(string[] args)
            {
                byte exit = 1;
                Thread th1 = new Thread(() =>
                {
                    int a = 0;
                    while (exit == 1) { a += 1; if (a >= int.MaxValue) { a = 0; } exit = Thread.VolatileRead(ref exit); };
                    Console.WriteLine(exit.ToString());
                });
                Thread th2 = new Thread(() =>
                {
                    Thread.Sleep(1000);
                    Thread.VolatileWrite(ref exit,0);
                });
                //th1.IsBackground = true;
                //th2.IsBackground = true;
                th1.Start();
                th2.Start();
            }
        }
    • Edited by TimoYang Thursday, August 30, 2012 7:58 AM
    Thursday, August 30, 2012 7:58 AM
  • 1) You don't need a++ specifically but you need something simple enough that doesn't prevent compiler optimizations. Otherwise you won't be able to see the effect of (not) using volatile in this case.

    2) Depends what you want that code to do, it's very different from my code. Your code modifies exit inside the loop, this means that both threads modify the same variable and you basically have a race condition. A race condition cannot be fixed with fences, remember what I said earlier: volatile is not a lock and it's not atomic.

    You also print exit, not a, this means that your program will always print 0 if it exits.

    Thursday, August 30, 2012 8:20 AM
    Moderator
  • Mike's code works as expected in my side(This code doesn't stop and doesn't print anything in the Release build, run outside debugger), the test environment is Windows 8 32bit, VS2012, .NET 4.5, Core 2 Duo CPU P8700.

    Make sure your Release build has Optimize code option (project properties page, Build tab) on, Debug Info (Advanced setting of Build tab) set to pdb-only, and choose x86 as platform target.

    As JIT generates machine code from IL according to current processor info, please use NGen.exe tool to make a native image for your test assembly (release build), and test again.

    By the way, do you have any conceptual question? or you just want know know why the sample doesn't work?


    Please remember to click “Mark as Answer” on the post that helps you, and to click “Unmark as Answer” if a marked post does not actually answer your question. This can be beneficial to other community members reading the thread.

    Regards,
    Eric Yang
    Microsoft Online Community Support


    Thursday, August 30, 2012 11:27 AM
  • I just modify Mike’s code as below, however, below code has same behavior with Mike’s

        class Program

        {

            static bool exit = false;

            static void Main(string[] args)

            {

                //bool exit = false;

                Thread th1 = new Thread(M1);

                Thread th2 = new Thread(M2);

                th1.Start();

                th2.Start();

            }

            static void M1()

            {

                inta = 0;

                while(!exit) { a++; }

                Console.WriteLine(a);

            }

            static void M2()

            {

                Thread.Sleep(100);

                exit = true;

            }

        }

    I run the test code, and you can find the jitted instructions of ConsoleApplication1.Program.M1() method below, you have different processor, JIT may generate different code for you.

    ConsoleApplication1!ConsoleApplication1.Program.M1()

    00300133 56              push    esi

    00300134 33f6            xor     esi,esi

    00300136 0fb6058a320d00  movzx   eax,byte ptr ds:[0D328Ah]  [Eric: this address 0x0D328Ah refer to the global variable 'exit', when M2() execute this instruction, the value (at address 0x0D328Ah) is 0, so eax is set to 0, the subsequence call uses the cached value 0 from eax]

    0030013d 85c0            test    eax,eax [Eric: eax is 0 now, enter infinit loop... //start loop]

    0030013f 7505            jne     ConsoleApplication1!ConsoleApplication1.Program.M1()+0x16 (00300146)

    00300141 46              inc     esi

    00300142 85c0            test    eax,eax

    00300144 74fb            je      ConsoleApplication1!ConsoleApplication1.Program.M1()+0x11 (00300141) [Eric: //end loop]

    00300146 e879a37f6e      call    mscorlib_ni!System.Console.get_Out() (6eafa4c4)

    0030014b 8bc8            mov     ecx,eax

    0030014d 8bd6            mov     edx,esi

    0030014f 8b01            mov     eax,dword ptr [ecx]

    00300151 8b4038          mov     eax,dword ptr [eax+38h]

    00300154 ff5014          call    dword ptr [eax+14h]

    00300157 5e              pop     esi

    00300158 5d              pop     ebp

    00300159 c3              ret

    0:004> r

    eax=00000000 ebx=02682334 ecx=026822cc edx=026822cc esi=f08c745c edi=04c5f1fc

    eip=00300142 esp=04c5f1ac ebp=04c5f1b0 iopl=0         nv up ei ng nz na pe nc

    cs=0023  ss=002b  ds=002b  es=002b  fs=0053  gs=002b             efl=00000286

    ConsoleApplication1!ConsoleApplication1.Program.M1()+0x12:

    00300142 85c0            test    eax,eax


    Please remember to click “Mark as Answer” on the post that helps you, and to click “Unmark as Answer” if a marked post does not actually answer your question. This can be beneficial to other community members reading the thread.

    Regards,
    Eric Yang
    Microsoft Online Community Support



    • Edited by Eric Ya - MSFT Monday, September 3, 2012 5:47 AM
    • Marked as answer by TimoYang Tuesday, September 4, 2012 5:05 AM
    Thursday, August 30, 2012 12:14 PM