-3

With all previously discussed caveats about using "volatile" (vs full barriers/interlock*/lock) in mind, there are cases where "volatile" fits the bill. One such example is given in (the footnote of) the accepted answer to Volatile vs. Interlocked vs. lock.

As volatile doesn't prevent these kind of multithreading issues, what's it for? A good example is say you have 2 threads, one which always writes to a variable (say queueLength), and one which always reads from that same variable.

If queueLength is not volatile, thread A may write 5 times, but thread B may see those writes as being delayed (or even potentially in the wrong order).

A solution would be to lock, but you could also in this situation use volatile. This would ensure that thread B will always see the most up-to-date thing that thread A has written. Note however that this logic only works if you have writers who never read, and readers who never write, and if the thing you're writing is an atomic value.

As a variation to that scenario, suppose there is one variable of atomic type, and two threads A and B that each check for and assign mutually different values to the variable. In the simplest case, say that the variable is "volatile bool Do;". Thread A runs a loop that checks Do and, if false, sets it to true. Thread B runs its own loop which checks Do and, if true, sets it to false.

Is this scenario thread-safe in all senses of "safe"?

For an example, below is a (minimal, silly, unconscionable ;-)) code mockup.

volatile static bool Do;

static void ThdA()
{
    for(System.Random Rnd = new System.Random();;)
    {
        if((Rnd.Next() % 10010101) == 0)
        {
            while(Do); // wait for B to complete
            Do = true; // signal B to do something
        }
    }
}

static void ThdB()
{
    for(;;)
    {
        if(Do)
        {
            System.Console.Write("doing something\n");
            Do = false; // signal A that B done
        }
    }
}

[ EDIT ] Following some of the comments, I should clarify that my question is as much about saving half a fence in a very special case as it is about verifying my understanding of "volatile" semantics in this particular case. I am not suggesting any new/generic approach, and I am well aware of the ins and (mostly) outs of volatiles in general, as discussed elsewhere (http://blogs.msdn.com/b/ericlippert/archive/2011/06/16/atomicity-volatility-and-immutability-are-different-part-three.aspx, https://msdn.microsoft.com/en-us/magazine/jj883956.aspx, https: //software.intel.com/en-us/blogs/2007/11/30/volatile-almost-useless-for-multi-threaded-programming/ etc).

The question is strictly whether "volatile" is sufficient in the very narrow case described here. The obvious expectations of "safety" are:
- the program never deadlocks;
- neither thread reads stale values (for the code mockup, this translates to each "Do = true" in ThdA having a matching "Do = false" in ThdB).

Community
  • 1
  • 1
dxiv
  • 16,984
  • 2
  • 27
  • 49
  • A note: if you do go down this road, you *must* learn about memory synchronization to make sure you don't get in a lot of trouble. Volatiles don't synchronize memory between threads, so you have to either do it yourself with a memory barrier, or make sure *all* shared content is marked volatile. – Cort Ammon Nov 08 '15 at 06:38
  • @Rob `Random Rnd = new System.Random()` executes only once. and yes its terrible for first iterations but as loop gets repeated then it doesn't generate same thing ;). – M.kazem Akhgary Nov 08 '15 at 06:40
  • @Rob The Rnd variable is initialized exactly *once* which is done *before* the loop starts. Not sure what you find wrong with it, except maybe the aesthetics of it being written inside the `for` clause - but again it's not executed in the loop. – dxiv Nov 08 '15 at 06:43
  • Though this code may technically be correct it is a really bad idea to roll your own primitives like this. There is a standard pattern for signaling threads that has already been written by experts; use it! – Eric Lippert Nov 08 '15 at 06:56
  • 2
    @CortAmmon: Volatile in C# introduces half fences on reads and writes. The thing you have to really deeply understand though is that *there is no guarantee that a consistent ordering of all writes will be observed by all threads*. In this case there is only one variable, but in cases where there are many it is very easy to fall into the trap of thinking that just because the value of each variable is fresh, that the side effects must be observed to occur in order on all threads. That's not true even on x86. – Eric Lippert Nov 08 '15 at 06:59
  • @Eric thank you for chiming in. If you upgraded that "may technically be correct" to "is technically correct" I'd be so happy. As for the rest of caveats, I am well aware and wouldn't otherwise have asked here in the first place. My use-case is rather offbeat and too long/boring to go into full details. Basically, there is no contention (one iteration in ThdA takes > 1sec, in ThdB < 10 msec) and speed is of the essence to the point where what you quote elsewhere as a 10-12 nanoseconds full-lock overhead adds up in the long run. – dxiv Nov 08 '15 at 07:22
  • Hard to know what you mean by "safe" – David Heffernan Nov 08 '15 at 07:36
  • @David: just the usual thread-safety expectations - no stale values, no deadlocks etc. – dxiv Nov 08 '15 at 08:29
  • You should state clearly what you mean in the question since thread safe has many possible meanings – David Heffernan Nov 08 '15 at 08:30
  • @whoever-downvoted-the-question please give a reason so that I can do better next time. – dxiv Nov 08 '15 at 08:32
  • @David: there is a generally accepted meaning of "safe" related to multi-threaded data access. The previous stackoverflow question that I partially quoted from referred to "safe" several times, and there seemed to be no confusion as to what it meant. Perhaps I am missing the finer points of your objection here. – dxiv Nov 08 '15 at 08:38
  • No. There is no single accepted view of what thread safe is. Read Eric's famous post on the subject. – David Heffernan Nov 08 '15 at 08:40
  • @David: well, Eric Lippert himself contributed a (helpful) comment shortly before yours, without nitpicking on what "it" is. Thank you for your input, and I'll leave it at that. – dxiv Nov 08 '15 at 08:59
  • You've read Eric's famous article I trust? – David Heffernan Nov 08 '15 at 09:12
  • @dxiv Can you explain a little about the algorithm? In my experience algorithms similar to the ones you describe fit into three categories: a) cases where you can tweak the fast algorithm (ThdB) to synchronize less often, such as once very 100 iterations or b) algorithms that are better done singlethreaded c) algorithms that deserve some real hardware support, such as using C++ for the program and running a Real Time Operating System (RTOS). This is especially true if even atomics are too slow for you. We may be able to help you work around this. – Cort Ammon Nov 08 '15 at 15:59
  • 1
    For reference, the last piece of code I've seen written in this style which worked (i.e. they got all the threading issues hammered out) is __cxa_guard, the gcc primitive used internally to implement function-scoped statics in C++. If you look at the LLVM version of the implementation, they felt it wise to put more likes of comment than code in the file, describing EXACTLY why they're getting away with what they do. – Cort Ammon Nov 08 '15 at 16:02
  • @Cort thanks for the gcc pointer. It looks to be more involved than the case in point here, but the source comments and http://manishearth.github.io/blog/2015/06/26/adventures-in-systems-programming-c-plus-plus-local-statics/ made for some good reading. – dxiv Nov 08 '15 at 22:47

2 Answers2

0

Yes, this is fully thread safe.

The danger with these kinds of approaches is when the boolean changes between when you read it and when you test it (while(Do);), and between when you read it and when you write the updated version (Do = false), but your code is safe from the failures:

  • in ThdA, if the variable changes between reading Do and testing it for the loop, you'll just loop one more time. But keep in mind the condition did fail if Do was false when reading it for testing in the loop, then turned true during the test. It just so happens that falling through sets it to true anyway in this case so it doesn't matter.

  • in ThdB you have a similar case, it'll just loop one more time. I don't see how that part can fail, but adding more code can easily break it.

Blindy
  • 65,249
  • 10
  • 91
  • 131
  • Thanks. As for "in ThdA [...] keep in mind the condition did fail if Do was false when reading it for testing in the loop, then turned true during the test" - not sure I follow that. When the while loop breaks, Do must be false. Once it's false, there is no other code to set it back to true, except in ThdA itself. I'll post a slightly more elaborate answer separately. – dxiv Nov 09 '15 at 01:59
0

Here is a slight generalization of the original question. Suppose there is one variable of atomic type, and N threads ThdX with X = 0 ... N-1. The value of the atomic type indicates which thread "owns" it at a given point in time. Each thread checks the value and, if it detects that it is the "owner" thread, then it is allowed to change the value. The variable is not changed elsewhere.

(This reduces to the original question for N = 2 and a bi-valued bool instead of a multi-valued atomic type.)

Claim is that the program won't deadlock or hang (at least not due to threading issues with the volatile ;-)) and that each assignment to the volatile variable will be picked up by the target thread. The proposed arguments are in the comments inlined below.

static int N = <max-threads>;   // number of threads

volatile static int n;          // assuming n is in the range 0 ... N-1
                                // if n == X then ThdX can safely change n

static void ThdX() // individual thread functions for X = 0 ... N-1
{
    for(;;)
    {
        /* arbitrary code that doesn't change n */

        if(n == X)              //<--- volatile read
        {
            {
                // acquire fence guarantees that no other thread
                // can be inside this inner block at the same time
                // because - all reads get fresh values
                //         - only one thread can match the if condition
                //         - n does not change inside this inner block
                // and therefore no other thread can change n
                // before this thread executes the assignment below

                /* arbitrary code that doesn't change n */
            }

            n = <new-value> % N;  //<--- volatile write

            // release fence guarantees that other threads
            // will read the new value next time they check
        }

        /* arbitrary code that doesn't change n */
    }
}

For full generality, "int n" should be "T n" where T is an atomic type, and "n == X" should be "f(n) == X" where f(T n) is a function mapping a T value to a thread index, but neither changes the main points.

dxiv
  • 16,984
  • 2
  • 27
  • 49
  • A couple of followup comments... Firstly, I'd like to thank those who actually contributed constructive comments. I'll be even more thankful if someone could either verify or provide a counterpoint to the claim above (something like 'fails on architecture X scenario Y because step Z'). – dxiv Nov 11 '15 at 05:24
  • Secondly, I'd like to "not thank" those who downvoted the original question without giving any reason whatsoever. Just because "volatile" is unpopular and easily misunderstood, shouldn't mean that *all* questions about it must be dismissed offhand. – dxiv Nov 11 '15 at 05:28