Click to See Complete Forum and Search --> : volatile variable in multithreaded programming


LarryChen
July 15th, 2010, 11:09 PM
Usually if a global variable is shared by more than one thread, this variable needs to be declared volatile. Basically I understand why we need to declare a variable as volatile because it may change without being detected by compiler. But what make a shared global variable change without being detected by compiler in a multithreaded environment?

D_Drmmr
July 16th, 2010, 10:46 AM
Usually if a global variable is shared by more than one thread, this variable needs to be declared volatile.

No, it doesn't. It should be protected by a synchronization mechanism, like a mutex. Volatile is not a synchronization mechanism, it doesn't really help in synchronization at all. Just search this forum and you will find plenty of discussions about it.

But what make a shared global variable change without being detected by compiler in a multithreaded environment?
Optimizations, either performed by the compiler or by the hardware.

LarryChen
July 16th, 2010, 12:31 PM
I guess there is a reason declaring global variable as volatile in multithreaded program. For example, if unsynchronized access to the global data won't cause any logical problems, then in this situation, we need to declare global data as volatile because it might be modified by another thread. Thanks.
No, it doesn't. It should be protected by a synchronization mechanism, like a mutex. Volatile is not a synchronization mechanism, it doesn't really help in synchronization at all. Just search this forum and you will find plenty of discussions about it.


Optimizations, either performed by the compiler or by the hardware.

Codeplug
July 16th, 2010, 01:16 PM
>> For example, if unsynchronized access to the global data won't cause any logical problems, ...
The fact that there's unsynchronized access is the main problem. Volatile doesn't help since it has nothing to do with synchronization.

gg

Arjay
July 16th, 2010, 06:42 PM
Do yourself a favor and avoid using global variables in multi-threaded programs.

One of the most problematic areas of mt programming is acquiring and releasing locks.

If you create an architecture where any piece of code can lock and unlock an object, then it is going to be difficult to track done issue where objects synchronized correctly (i.e. locks are getting made, are locked too early or late, or not unlocked).

Instead it's better to encapsulate the thread synchronization within a small part of the code.

For example, in your other post you mention a design that uses singletons to perform work.

These are an excellent approach to hiding thread synchronization details.

For example, instead of using a global mutex to protect a global variable, put the variable and mutex inside a singleton class and expose accessor methods to the variable (e.g. getVariable()/setVariable()). Any thread that needs to use the data just calls the one of the accessor methods.

Because the thread synchronization and mutex calls are handle within the accessor methods, external uses of the methods don't need to worry themselves over synchronization details.

long data = Singleton::getInstance( )->getVariable( );

The external calling code ends up being very clean, and there is less of a chance that a synchronization error will occur because the synch code is all inside the Singleton class.

ahmd
July 18th, 2010, 12:01 AM
Listen to those guys above and to what they suggested and learn on my bad example. I thought that thread synchronization and using global variables was the "easiest and most logical" choice for multi-threaded programming ... only to learn later on that it is not. Believe me debugging a nightmare that may ensue out of your assumptions is not something I'd wish upon my worst enemy...

Also to help you illustrate the concept of the volatile keyword, consider the following example:
int v = 0;
for(int i = 0; i < 10; i++)
v++;

_tprintf(_T("%d\n"), v);

Compile it as Release build and place a breakpoint on the int v = 0; line. You'll see that the compiler optimized it away, along with the for() operator into this: (And your breakpoint will jump down to the _tprintf() statement.)
_tprintf(_T("%d\n"), 10);
And then try this:
volatile int v = 0;
for(int i = 0; i < 10; i++)
v++;

_tprintf(_T("%d\n"), v);

in this case the compiler abstained from optimization and obediently kept your code the way it is, thus a breakpoint will remain on the volatile int v = 0; line and won't skip to _tprintf().

NOTE that this is a good example why volatile keyword can be dangerous if misused. In most cases you don't really need to use it at all.

LarryChen
July 18th, 2010, 03:40 PM
I am trying a little bit harder to make myself clear this time. First of all I assume I don't care synchronization, which means it is ok for multiple threads to access a data at the same time. Let me start with an example,

int var = 0;

if(var == 0)
{
...
}

Take a look at this innocent look code in a multithreaded environment plus my assumtion I made early. Then the compile will optimize this piece of code into the following,

int var = 0;

if(true)
{
...
}

This optimization is undesirable because another thread might change variable var when one thread executes the code above at the same time, right? So if I declare var as volatile, then we can expect the compiler not to optimize the code as above. This is why I say volatile still his its role in multithreaded programming. Any comments are welcome.

Codeplug
July 18th, 2010, 04:45 PM
>> I don't care [about] synchronization, which means it is ok for multiple threads to access a data at the same time.
That's faulty logic. The only way to make it "ok" is to use proper synchronization. The only exception is when all threads read from the same memory location - then synchronization isn't required since the memory never changes.


>> This optimization is undesirable because another thread might change variable var when one thread executes the code above at the same time, right?
That's only one of many things that can go wrong when you don't use synchronization.
volatile does nothing to prevent compiler re-orderings between volatile and non-volatile accesses.
volatile does nothing to prevent hardware re-orderings of reads and writes.
volatile does nothing to ensure the atomicity of the entire read and write operations.

All volatile does is strip the compilers ability to optimize - usually making things slower. If you're not reading and writing to physical devices (memory mapped registers, flash devices, etc...) then you don't need volatile in your code.

If multiple threads access the same memory location where at least one thread is writing, then proper synchronization is required for that code to be "correct".

gg

Arjay
July 19th, 2010, 02:26 PM
This optimization is undesirable because another thread might change variable var when one thread executes the code above at the same time, right? So if I declare var as volatile, then we can expect the compiler not to optimize the code as above. This is why I say volatile still his its role in multithreaded programming. Any comments are welcome.Forget volatile and look at thread safety for a moment.

Accessing an integer from more than one thread is simply not thread safe.

The reason is that changing an integer to a different value isn't an atomic operation. So one thread could be halfway through writing the value, when it gets preempted and another thread gets scheduled to read the value - only the value is garbage because it the write has written only half the value.

Illustrating this with an integer is difficult, but it's easy to illustrate the problem using a character array. One might be tempted to say that the chance of a race condition to occur with an integer variable is remote so comparing it with a char array isn't valid. In reality the two are identical because the goal in mt programming is to properly synchronize data. This elimates those hard to track down bugs where a program works fine on one system but has intermittent failures on another system. Thread safety is still thread safety no matter the type of shared resource (int, char, struct, etc.).

A few years ago, I wrote a Win32 thread synchronization article for CG. Might as well resurrect the output from one of the article's examples where one thread writes to a character array while another thread is reading from it.

Each line respresents the output from the read thread. The blue portion represents the new part of the array that is being overwritten by the write thread. The black area is the original array chars.

This first example shows what happens when no synchronization is used.

-------------------------------------------------------------------
SlowCopy Example - Illustrates using non-synchronized shared memory
between two threads.

Source: The source string that will replace the dest string.
Dest: Original destination string that will be overwritten.

Secondary Thread Started
1) Original destination string that will be overwritten.
2) The snal destination string that will be overwritten.
3) The sourcdestination string that will be overwritten.
4) The source stination string that will be overwritten.
5) The source strintion string that will be overwritten.
6) The source string tn string that will be overwritten.
7) The source string that ring that will be overwritten.
8) The source string that will that will be overwritten.
9) The source string that will reat will be overwritten.
10) The source string that will replacill be overwritten.
11) The source string that will replace t be overwritten.
12) The source string that will replace the doverwritten.
13) The source string that will replace the dest written.
14) The source string that will replace the dest strtten.
15) The source string that will replace the dest string..
16) The source string that will replace the dest string.
Secondary Thread Exited

Destination string after SlowCopyNoSync:
The source string that will replace the dest string.

End of program!
-------------------------------------------------------------------

This second example shows what happens with proper synchronization using a critical section.

-------------------------------------------------------------------
SlowCopyNativeCS Example - Illustrates synchronizing shared memory
between two threads using a native critical section.
Source: The source string that will replace the dest string.
Dest: Original destination string that will be overwritten.

Secondary Thread Started

1) Original destination string that will be overwritten.
2) Original destination string that will be overwritten.
3) Original destination string that will be overwritten.
4) The source string that will replace the dest string.
5) The source string that will replace the dest string.
6) The source string that will replace the dest string.
7) The source string that will replace the dest string.
8) The source string that will replace the dest string.
9) The source string that will replace the dest string.
10) The source string that will replace the dest string.
11) The source string that will replace the dest string.
12) The source string that will replace the dest string.
13) The source string that will replace the dest string.
14) The source string that will replace the dest string.
15) The source string that will replace the dest string.
16) The source string that will replace the dest string.
Secondary Thread Exited

Destination string after SlowCopy:
The source string that will replace the dest string.

End of program!
-------------------------------------------------------------------


The first example shows data corruption due to the race condition.

The second example after synchronization shows no data corruption.

You can see the necessity of synch pretty easily with a char array (and the longer array the more likely it will happen). However, the same problem can occur with other data types as well.

Unless the variable is a boolean (and a true boolean, not a BOOL), then synchronization is required for shared resources if there is at least one concurent write thread.

__________________________________________________________
Arjay

See my latest series on using WCF to communicate between a Windows Service and WPF task bar application.
Tray Notify - Part I (http://www.codeguru.com/csharp/.net/net_general/threads/article.php/c17179/Tray-Notify-Using-Windows-Communication-Foundation---Part-I.htm) Tray Notify - Part II (http://www.codeguru.com/csharp/csharp/cs_delegates/eventhandling/article.php/c17185/Tray-Notify-Using-Windows-Communication-Foundation---Part-II.htm)

Need a little help with Win32 thread synchronization? Check out the following CG articles and posts:
Sharing a thread safe std::queue between threads w/progress bar updating (http://www.codeguru.com/forum/showthread.php?p=1688454#post1688454)
Simple Thread: Part I (http://www.codeguru.com/cpp/misc/misc/threadsprocesses/article.php/c14105/) Simple Thread: Part II (http://www.codeguru.com/cpp/misc/misc/threadsprocesses/article.php/c14291/)
Win32 Thread Synchronization, Part I: Overview (http://www.codeguru.com/cpp/w-p/win32/tutorials/article.php/c9823/) Win32 Thread Synchronization, Part 2: Helper Classes (http://www.codeguru.com/cpp/w-p/win32/tutorials/article.php/c9825/)

www.iridyn.com (http://www.iridyn.com)

Arjay

Lindley
July 21st, 2010, 01:09 PM
First of all I assume I don't care synchronization, which means it is ok for multiple threads to access a data at the same time.

Sure, it's okay to let multiple threads read a variable at the same time, so long as none of them is trying to modify it.


This optimization is undesirable because another thread might change variable var

But here you state that another thread may try to modify the variable. Therefore, synchronization is definitely required.

Furthermore, the volatile keyword is of no help here, so put it out of your mind. Unless you work with embedded devices, you'll probably never need to use it.

JohnW@Wessex
July 23rd, 2010, 06:08 AM
Relevant reading.

http://www.drdobbs.com/184403766

Codeplug
July 23rd, 2010, 11:02 AM
My stock response to that article: http://www.codeguru.com/forum/showpost.php?p=1873694&postcount=8

gg