|
Thanks CPallini,
I personally prefer to use critical section or mutex, which has more control of concurrency behavior compared with volatile. Volatile only has very limited function in a multi-threaded environment to share data.
regards,
George
|
|
|
|
|
George_George wrote: 1. In your experience, when do you use volatile?
2. If we share data between threads and we are already using synchronization approach like mutex, do we need to add additional volatile keyword?
I don't use volatile in real-world code.
I started coding C/C++ in 1998. Only in the first year when I'd read volatile keyword, I tested it in exercise code. Well, at that time I did not know what debug/release builds were. I did not see the difference. Later on in 1999, I learned some more things, so I tried to use it. At that time, the threading design of mine was so poor. Yes, volatile was used in my early code, for once or twice. Finally I realized that with proper design for synchronisation mechanism of threading, volatile is not necessary.
Maxwell Chen
|
|
|
|
|
Maxwell Chen wrote: I don't use volatile in real-world code
I never did either when I was just writing for windows. An event serves the purpose of using a "flag" variable between threads. However, now that I'm writing cross-platform code, I find myself using "volatile flag" variables quite often for my multi-threaded stuff. The ease of use of the Windows event is not found on other platforms. I find it simpler to use the volatile keyword to prevent the compiler from optimizing away my variable rather than trying to find the best-for-each-platform way of implementing a simple signalling event between threads.
Judy
|
|
|
|
|
Hi Judy,
In your cross-platform development experience, I think you mean if we are not using volatile, the function of the program will change in your experience?
Could you show a more specific example please? I am interested in this since in my experience I have not found any case which behaves differently with/without volatile.
regards,
George
|
|
|
|
|
George_George wrote: In your cross-platform development experience, I think you mean if we are not using volatile, the function of the program will change in your experience?
yes, it happened to me in a VS2008 release mode version of a multi-threaded program
George_George wrote: Could you show a more specific example please? I am interested in this since in my experience I have not found any case which behaves differently with/without volatile
Sorry, I can't show you the specific chunk of code since it is proprietary, but the use of the volatile variable was very simple. A class created a worker thread during its processing. The worker thread was encapsulated in a class that contained a public boolean variable that was set to false in the constructor **. At some further point in its processing, independent of the thread, the class would change the value of the boolean (using its pointer to the thread class it had created) from false to true, indicating that the thread should stop what it was doing. The thread contained an infinite loop that checked the value of the boolean during its loop. When the boolean went true, the thread would terminate.
Without the use of volatile in the definition of the boolean within the thread class, the thread never terminated in release mode. When the code ran in debug mode, it worked correctly. When I examined the assembly language generated in release mode, I found that the compiler had optimized away the check of the boolean in the thread's loop.
If you are going to modify a variable used between threads, you should use volatile for safety's sake. Note that when I used Windows exclusively, I always used events for a simple "value change" flag between threads because it was guaranteed to be thread-safe. My worker threads don't usually directly affect data used by the initiating thread - they tend to communicate via windows messages (or the equivalent on other platforms) and pass data that way.
Your statement in other messages in this thread that you can omit the volatile keyword is not correct. Depending on what the optimizing compiler does, it can make a difference and this is the worst kind of bug to try and find. It all depends on what the optimizer does which changes every time you change the code.
Most developers don't encounter this kind of bug since this is not the best or safest way to share data between threads. I only encountered it when I started to "cheat" in my code because I didn't want to have to write platform-specific code every time I wanted to simply signal a thread.
Judy
** < edit >Thinking about it some more (since I don't have the code here with me at home), the variable was initializaed at the begining of the Run function which contains the infinite loop, not the constructor. Big difference since this way the optimizer sees the variable set to false and never changed, so therefore takes away the check of the variable inside the loop. < /edit >
modified on Saturday, December 29, 2007 2:42:20 AM
|
|
|
|
|
Thanks Judy,
Your sample is so great! I am wondering if you use mutex or other technologies, like critical section or something, to synchronize/share the cross-thread variable, do you think we still need the volatile keyword?
I have this concern is because some other people in this discussion mentioned that volatile is an alternative way to share data between threads (alternatives are like mutex), so if other approaches like critical section or mutex is used, volatile is no need.
How do you think of this idea?
regards,
George
|
|
|
|
|
Volatile has nothing to do with synchronization. It simply instructs the compiler that every access to the variable must be to the "real" memory allocated to that variable - it can't use registers to store the value, it can't not perform the memory access, and so on. Synchronization typically ensures that only one thread (or process) is using a resource at a time. My use of the volatile keyword does not force only one thread at a time to touch the variable. Technically, my example is "bad" code since it does not contain any logic to synchronize access to the variable. In my example, however, I can get away with it because of the way I use the variable. There is only one thread that changes the value and only one thread that checks the value. Also, the thread logic is such that if the thread loop runs one extra time due to a thread preemption (i.e. the changing thread sneaks in and changes the value after the checking thread has loaded the memory value but before it has checked the value), then there are no ill effects.
Things like mutexes and critical sections ensure single access to a resource. What that resource is has no relationship to the synchronization mechanism that protects it. The resource could be a shared memory location or a handle to a socket or a fifo or a pipe or ... or something as simple as a counter of the number of threads. To put it simply, the synchronization mechanism controls how the resource is protected and the resource is whatever you want it to be. The only relationship between the two is that the sync controls how many threads are allowed to touch the resource simultaneously.
Judy
|
|
|
|
|
I agree with your great analysis, Judy!
My concern is (maybe I have not made myself understood), from the MSDN sample, we can see that if we do not add volatile to the thread shared variables, the checking thread may deadlock -- I think it is what MSDN sample teaches us -- no volatile means no reliable and secure code.
But in the past the in many multi-threaded, I do not see they use volatile (they are using other approches like mutex). So, I am confused whether the their code is,
1. unsafe, and need to add volatile;
2. or safe, and mutex is an alternative way to tell compiler do not optimize (this is what I mean alternative).
Any comments? Please feel free to correct me if I am wrong.
regards,
George
|
|
|
|
|
George_George wrote: we can see that if we do not add volatile to the thread shared variables, the checking thread may deadlock -- I think it is what MSDN sample teaches us -- no volatile means no reliable and secure code.
I read that sample as saying that not using volatile means the code will not execute correctly since the compiler may mangle the code so that the variable does not operate as a mutex. Whether the code deadlocks or allows simultaneous access to the CriticalData variable depends on how the compiler optimizes the code.
George_George wrote: But in the past the in many multi-threaded, I do not see they use volatile (they are using other approches like mutex). So, I am confused whether the their code is,
1. unsafe, and need to add volatile;
2. or safe, and mutex is an alternative way to tell compiler do not optimize (this is what I mean alternative).
Most code doesn't use a combination of a volatile variable plus the Sleep to implement a mutex. Note that just using volatile does not turn the variable into a mutex - you must have the Sleep . Why go to that trouble when the OS has a mutex built in that does all that overhead stuff for you? The usual and "approved" approach is to use the functionality provided to you instead of rolling your own, unless you have a compelling reason to override the existing functionality. It's much safer to use the various sorts of synchronization mechanisms that all OSes provide rather than writing your own and possible not doing so correctly.
Use of a mutex, per se, does not tell the compiler not to optimize. The fact that you are making a function call limits the optimizer in what it can do to the code surrounding the function call.
Judy
|
|
|
|
|
Thanks Judy,
I agree and understand all of your points. Except the below one. My confusion is, I can not think of the other ways the compiler could optimize the code, other than making a deadlock.
What other ways compiler may optimize the code? Could you show some more ideas please?
JudyL_FL wrote: I read that sample as saying that not using volatile means the code will not execute correctly since the compiler may mangle the code so that the variable does not operate as a mutex. Whether the code deadlocks or allows simultaneous access to the CriticalData variable depends on how the compiler optimizes the code.
regards,
George
|
|
|
|
|
Depends on what the compiler thinks the value of Sentinel is when it encounters it, while doing its optimizing run. If it thinks it is true, it would optimize so that it deadlocks. If it thinks it is false, it would optimize so that it allows multiple access. Without knowing the details of how the optimizer works, it could be either one. It may see the value as true if it does all its global variable initializations at the start of its processing and doesn't change them. It may see the value as false if it notes the assignment to false (and only false) in the second thread during its first pass and then does the optimizations in its second pass. It all depends on how the compiler is implemented. I admit the "optimize away" case is reaching a bit but you never know. Take a look at the assembly language generated by an optimizing compiler sometime; it does some mighty weird stuff sometimes. I wouldn't put anything past it.
Judy
|
|
|
|
|
Thanks for your advice, Judy!
regards,
George
|
|
|
|
|
Thanks Maxwell,
Let us just omit this keyword. We can not find a more specific case when this keyword is mandatory needed.
regards,
George
|
|
|
|
|
George_George wrote: 2. If we share data between threads and we are already using synchronization approach like mutex, do we need to add additional volatile keyword?
The volatile keyword is not so much for synchronization as it is for telling the compiler that it needs to evaluate the variable at every reference (because it could be modified in the program by something other than statements, such as the operating system, the hardware, or a concurrently executing thread) rather than optimize them away.
"Normal is getting dressed in clothes that you buy for work and driving through traffic in a car that you are still paying for, in order to get to the job you need to pay for the clothes and the car and the house you leave vacant all day so you can afford to live in it." - Ellen Goodman
"To have a respect for ourselves guides our morals; to have deference for others governs our manners." - Laurence Sterne
|
|
|
|
|
Thanks DavidCrow,
Could you show a more specific example please (better a sample which you experienced)? I am interested in this since in my experience I have not found any case which behaves differently with/without volatile.
regards,
George
|
|
|
|
|
You are trying to udnerstand everything at once - I am impressed!
George_George wrote: I have tried to remove the keyword volatile, and the result is the same
Here's an important question for programming:
Even if it (seems to) work, it may be broken.
"broken" in this case means: it may fail as soon as you move to another PC, another compiler, another version of the runtime library, or the problem may occur just once in 10.000 hours. Since compilers usually don't guarantee "same binaries for same source", it might even fail after recompiling.
Especially in C++, there are many constructs that may work right here right now, and the code your compiler for your platform generates is correct. Still, the source code is wrong (and a maintenance time bomb)
For the MSDN sample: omitting the volatile keyword, the code may fail on multicore machines with separate caches that are not necessarily coherent (wikipedia[^])
|
|
|
|
|
Thanks peterchen,
I can understand your points and I agree. Here is my previous comments and I think the optimization provided by compiler is wrong -- causing wrong function in a multi-threaded environment. Do you have any comments?
--------------------
Now I strongly suspect whether compiler will generate any wrong code -- functional wrong code. In MSDN sample, variable Sentinel is used to act as a shared variable between thread1 and thread2. Compiler should guarantee that both threads can read/write the correct value of Sentinel.
It seems that volatile will make wrong optimization to prevent thread1 from reading the most recent correct value set by thread2? I think it will bring high risks to careless developers, who does not know about volatile and forget to put it ahead of the variable, which will result in the wrong optimization of compiler.
--------------------
regards,
George
|
|
|
|
|
George_George wrote: and I think the optimization provided by compiler is wrong -- causing wrong function in a multi-threaded environment. Do you have any comments?
The optimization is not wrong. The C++ Standard says the compiler may do that*), so you have to live with this.
C++ as a language does not understand anything about threads. So for the compiler, changing Sentinel in a separate thread is like changing it secretly behind its back. volatile is the warning to the compiler "be careful! this value may change behind your back!"
A bug in your source code that appears only under (agressive) optimizations is actually quite frequent. There are a lot of subtle pitfalls in the C++ standard that code that looks good and works well in an unoptimized build breaks under the otimizer. (my recent post in the "Subtle Bugs" forum is a good example).
You have to differentiate three things: What you think is correct, what your compiler should or may do according to the standard, and what your compiler actually does. In most cases, you are wrong - not the compiler
I've seen maybe hundreds of posts of people claiming to "have found a compiler bug", virtually always it was a bug in their source code, due to ignorance or misunderstanding of the C++ Language Standard.
George_George wrote: I think it will bring high risks to careless developers, who does not know about volatile and forget to put it ahead of the variable, which will result in the wrong optimization of compiler
Absolutely. It would be good if C++ had better sematincs for multithreaded applications - but it doesn't have. C++ is not a simple language - not because of its syntax, but because of things like these. Multithreading is naturally not simple. C++ is not a language for careless programmers (and I think there is no language dedicated to them)
*) rather, it doesn't say the compiler may not do that...
[edit] I want to add that right now, no compiler implements the C++ standard perfectly - so there is always a discrepancy between what the standard says and the compiler does. Still, with most "errors", it is not the compilers fault. But too, just because your compiler accepts it doesn't mean it's legal.
Comeau[^] is known as most compliant, they also have a web applet where you can test short code snippets.
modified on Friday, December 28, 2007 5:28:27 PM
|
|
|
|
|
peterchen wrote: C++ is not a language for careless programmers (and I think there is no language dedicated to them)
There is!! ...... the "Plain English Compiler".
Maxwell Chen
|
|
|
|
|
I refrain from commenting on that
|
|
|
|
|
peterchen wrote: I refrain from commenting on that
Maxwell Chen
|
|
|
|
|
Thanks peterchen,
1.
I think you mean in the MSDN sample, the change of variable *Sentinel* by thread2 may not be noticed by thread1. So, thread1 may deadlock? I can vaguely understand... but if my understanding is correct, could you write down the code which is the *optimized* code by compiler please (when without volatile keyword)?
2.
I think you key points are, optimization from compiler's perspective is correct. Since compiler know nothing about multi-threading? Right?
regards,
George
|
|
|
|
|
2. Almost, yes
My key point is: The optumization is correct, and knowing this is a vital part of being a good programmer.
(and that's a very important thing to learn)
1. What follows is a technical explanation, that is possibly valid for your compiler and your platform. The same behavior could be caused by other effects on your platform. Other compilers may react differently and may or may not expose similar results.
The compiler sees the following loop:
while(Sentinel==0)
Sleep(0);
The optimizer loads the contents of the memory location to a CPU register. Since it sees no location where Sentinel changes (and doesn't need the CPU register for something else). In each loop iteration, it keeps on checking the CPU register, thinking "no, Sentinel can't have changed, and I have the value still in a register, so I check that register". But the register value never changes.
A similar effect can occur with dual core CPU's where each core has it's own CPU cache - the compiler has to emit special code to tell the CPU "..and make sure you synchronize all caches"
For an interesting read how complex these things can get, try the The "Double Checked Locking Pattern is Broken" declaration[^]
|
|
|
|
|
Thanks for your great reply peterchen,
I do not agree how compiler optimize the loop is correct. For example,
while(Sentinel==0)
Sleep(0);
I think the way compiler optimizes the while loop (or other similar loop) is based on the *fact* that the control condition of a loop will not be changed by another thread -- this *fact* is incorrect.
The common way in engineering to design whether or not a thread needs to be terminated is to let the thread in a loop and continues to check a bool variable called stopped. Actually, I do not see many people add volatile keyword to the stopped bool variable.
Any comments?
have a good weekend,
George
|
|
|
|
|
George_George wrote: Actually, I do not see many people add volatile keyword to the stopped bool variable.
You've come very far in what seems a short time, George
Maybe I should distinguish two things here: From what you (and I) would like to have, the compiler is doing something wrong, agreed. But from the view of the language definition, the compiler is absolutely correct.
My point of this post: The compiler cannot help you as much as you now think it could.
There are several reasons:
Holding frequently used values in a register is very important for optimizations - otherwise the compiler could generate neither fast nor compact code.
If the compiler would blindly assume that "some thread may modify values", the following variables could nopt be held in registers:
* all variables not declared in the local function block
* all variables of which a reference / addres is passed to some function
That would pretty much kill many optimizations that are essential to efficient object-oriented code.
Furthermore, all access to these variables would require to force synchronization between core caches, pretty much killing the advantage of multiple cores.
So you can either opt for the compiler to make things easy for you, or the compiler giving you full control. C++ almost always opts for "full control, but you need to know what you are doing". (That's why C++ is usually compared to a ferrari)
Keep in mind that, even above loop can be exited legally. Sleep(0) might throw an exception when it is finished. It might terminate the program using exit(). Generally, the compiler doesn't know the source code of Sleep.
It isn't as bad as you think. You can split data access from separate threads into the following two cases:
access to complex data
that's most structs and classes. In this case, you need to use a lock (e.g. CRITICAL_SECTION, or Mutex) anyway, since the access to these is not atomic. Acquiring/Releasing the lock takes care of the ugly stuff that would be caused by multiple threads.
Note that the compiler - even if it were aware of threads - couldn't help you with ANYTHING in that case.
atomic reads & writes
that's usually byte-, word- or integer- sized access, when they are well-aligned. The Sentinel example falls into that category.
In this case, the lock is not strictly necessary. BUT we now have to handle all the processor and memory issues involved with multithreading.
For this case, C++ offers volatile , which is a bit of a crutch. Additionally, the Win32 API offers Interlocked routines that allow safe access to a well aligned 32 bit integer. When linked correctly, this emits minimal assembly instructions. Using Interlocked, the sample could be fixed like this:
replace Sentinel = n with InterlockedExchange(&Sentinel, n)
replace Sentinel++ with InterlockedIncrement(&Sentinel);
replace reading Sentinel with InterlockedExchangeAdd(&Sentinel, 0)
On 64 bit systems, you also have 64 bit Interlocked functions, as well as finer-controlled routines
Phew.
|
|
|
|
|