views:

164

answers:

5

A simple situation here, If I got three threads, and one for window application, and I want them to quit when the window application is closed, so is it thread-safe if I use one global variable, so that three threads will quit if only the global variable is true, otherwise continue its work? Does the volatile help in this situation? C++ programming.

+3  A: 

If you only want to "read" from the shared variable from the other threads, then it's ok in the situation you describe.

Yes the volatile hint is required or the compiler might "optimize out" the variable.

Waiting for the threads to finish (i.e. join) would be good too: this way, any clean-up (by the application) that should occur will have a chance to get done.

jldupont
Actually only window thread will write there, when it will quit, changing the value to true. Is that ok?
Daniel
@Daniel: yes it is OK.
jldupont
Thank you very much!)
Daniel
You really *need* the volatile keyword - it's not just a helpful hint. Although the *processor* won't ignore another core's accesses (merely reorder them), the *compiler* might remove the variable reference entirely if it detects that it cannot change - which, since it's set in another thread, it doesn't appear to.
Eamon Nerbonne
I thought some compilers were smart enough to detect this condition. As far as I am concerned, I always use the `volatile` hint in such situation... embedded software background helping.
jldupont
Volatile is pretty much pointless. It provides half of what you need (the memory access won't be optimized away), but not all of it (it makes no guaranteed of the ordering of memory accesses, or when the write is visible). Use a memory barrier instead, that's what they're for.
jalf
@jalf: in the case at hand here, what does it matter? I mean: what would a memory barrier provide that's worth this effort?
jldupont
Safety. ;) I'd just rather play it safe to avoid nasty surprises just because the compiler decided to reorder a bit so the "quit" flag gets set earlier than expected. When it comes to threading, I just like to be sure.
jalf
@jalf: I understand the overarching guideline but in this case, I believe it is overkill: if a single boolean **value** (0 or !0) can't be guaranteed a proper write to memory, then how are the memory barriers implemented anyhow?
jldupont
A: 

Yes this is a common technique.

But you should also wait for all child threads to exit before the main thread exits main().
In most thread implementations if the main thread exits main() all currently live child threads are repeated (see your threading documentation for details) without the benefit of allowing their stacks to unwind correctly. Thus all the nice benefits of RAII will be lost.

So set your global variable, but then wait (most threading systems have a join method to allow you to wait (for threads in a non busy state) to die) for all children to exit cleanly before allowing the main() thread to exit.

Martin York
That is what I was planning to do, set the variable to true, and wait the threads ending. Thanks for the answer!
Daniel
A: 

It's safe right up to the point that you change the variable's value to get the threads to quit. At that point 1) you need to synchronize access, and 2) you need to do something (sorry, volatile isn't enough) to assure that the new value gets propagated to the other threads correctly.

The former part is pretty easy. The latter substantially more difficult -- to the point that you'll almost certainly need to use some sort of library- or OS-provided mechanism.

Jerry Coffin
Volatile should be enough. It tells the compiler that the variable may be updated from an unexpected (or non-deterministic) source and thus it can not be cached or optimized away.
Martin York
@MartinYork - volatile may not be enough as it does not provide visibility semantics: http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2006/n2016.html
R Samuel Klatchko
@Martin: Volatile is basically enough to tell the compiler that the variable can't just sit in a register. It does nothing, however, about a variable just sitting in the cache. Some hardware (x86, for example) maintain strict cache coherence, so with them it's enough. Other hardware doesn't, and with them volatile isn't enough. In general volatile isn't a very good tool for threading -- it's too restrictive in some ways, but not restrictive enough in others.
Jerry Coffin
@R Samuel Klatchko: I disagree with your interpretation of that paper. It is questioning the ability of volatile to gurantee the ordering of read/write to a volatile variable. since we have only one writter and multiple readers of a state flag, in this case it is not a big deal as the thread will just do another iteration of work before seeing the updated flag and thus exit as expected.
Martin York
@Jerry Coffin: I see the problem of cache coherence as problem that the compiler should take into account and it should plant the appropriate instructions to flush the cache so that each processor has a coherent view of a volatile value. I agree that there are problems with volaile and threading especially around access ordering but that problem does not apply in this situation because only one thread is updating the value.
Martin York
@Martin:Perhaps the compiler *should* include instructions to flush the cache, but in fact, 1) most compilers don't, and 2) I don't see anything in the standard that requires them to do so. We don't know enough to say there won't be problems with access ordering -- after killing the other threads, the main thread will (quite rightly) be written assuming synchronized access is no longer needed. If *all* it does is exit, that may not be a problem -- but cleaning up is likely to involve destroying data that was shared. That will cause problems if another thread remains active.
Jerry Coffin
@Jerry: I disagree with 1) and 2) (or the compiler OS work together to achieve this) as otherwise single threaded application would not work determinstically on multiprocessor architectures. The main thread must wait for all children to exit before it does (otherwise they are repead (not stack unwinding in most implementations)). Therefore ordering is not important as cache coherence will eventually be reached.
Martin York
@Martin:Determinism for single threaded applications is trivial by comparison -- a global only ever has to be visible to the one processor on which that application is running. The only conceivable circumstance under which a cache flush may be needed is when the process may be migrated to a different processor, which can only ever happen during a task switch -- as such, it's trivial for the OS to handle it with no compiler help at all. That's not even close to true when multi-threading is involved.
Jerry Coffin
@Jerry: Exactly using exactly the same technique that it must use for single threaded application we achieve eventual cache coherence. The difference in multithreaded application is that you can not gurantee that the order of reads/write (in the general case) (and thus you can gurantee deterministic behavior). But in this specific situation that makes no difference as the main thread has too wait for all the children anyway.
Martin York
@Martin:I'm sorry, but that's simply not true. Just for an obvious example, if you simply lock each thread to always execute on a single processor (core), you assure determinism even with cache that provides no coherence guarantees at all. Locking each thread to a single core, by contrast, does no good at all -- to do any good, you'd have to lock all the threads to the same core (in the process, throwing away all performance benefits).
Jerry Coffin
TO me that does not make any sense. But that is probably due to the limit format we are using here in the comment section. I think to get conformance on the answer you should open a proper question and get input from a wider audiance.
Martin York
+1  A: 

No, this is risky because of memory visibility issues. On a multi-processor, writing to memory on one processor does not mean a different processor will see that change immediately. Furthermore, without using mutex's, it's possible that it can take quite a long time before the change is propagated to the other processors.

R Samuel Klatchko
Certainly on x86, and AFAIK on every shared-memory architecture under the sun a remote write will invalidate a local cache. So, this works fine - you may see the "exit" flag at a slight delay, and you may see the exit flag's write reordered with other side effects, but you will see it eventually.
Eamon Nerbonne
A bigger risk is that the compiler might not even write to memory if the variable is not volatile
erikkallen
+2  A: 

Theoretically, volatile is not enough. There are two abstraction layers:

  • between the source code actions and the actual opcodes;
  • between what a core/processor sees and what the other cores/processors see.

The compiler is free to cache data in register and reorder read and writes. By using volatile you instruct the compiler to produce opcodes which perform the read and writes exactly in the order you specify in your source code. But this handles only the first layer. The hardware system which manages communication between the processor cores may also delay and reorder reads and writes.

It so happens that on x86 hardware, cores propagate writes to main memory fairly fast, and other cores are automatically notified that memory has changed. So that volatile appears to be enough: it makes sure that the compiler will not play funky games with registers, and the memory system is kind enough to handle things from that point. Note, though, that this is not true on all systems (I think that at least some Sparc systems could delay write propagation for arbitrary delays -- possibly hours) and I have read in one of the AMD manuals that AMD explicitly reserves the right to propagate writes less promptly in some future processors.

So the clean solution is to use a mutex (pthread_mutex_lock() on Unix, EnterCriticalSection() on Windows) whenever accessing your global variable (both for reading and for writing). Mutex primitives include a special operation known as a memory barrier, which is like a volatile on steroids (it acts as a volatile for both abstraction layers).

Thomas Pornin
If your memory system plays funky games with cache coherence is it not the responcability of the compiler then to plant the appropriate code to force the cache into coherence in the situation where volatile is used?
Martin York
Memory barriers are expensive, so compilers are wary of using them. `volatile` was initially meant for interaction with memory-mapped I/O devices, and with signal handlers (signal handlers are asynchronous but happen on the *same* core). There is plenty of code which uses `volatile` for that, and which would be penalized by a `volatile`-with-a-barrier. `volatile` was not designed to be a barrier.Note that other languages do differently. Java's `volatile` includes a memory barrier (it even ensures atomic read and writes, even for "big" value types such as `long` and `double`).
Thomas Pornin