views:

283

answers:

7

The title says it all really. If I am accessing a single integer type (e.g. long, int, bool, etc...) in multiple threads, do I need to use a synchronisation mechanism such as a mutex to lock them. My understanding is that as atomic types, I don't need to lock access to a single thread, but I see a lot of code out there that does use locking. Profiling such code shows that there is a significant performance hit for using locks, so I'd rather not. So if the item I'm accessing corresponds to a bus width integer (e.g. 4 bytes on a 32 bit processor) do I need to lock access to it when it is being used across multiple threads? Put another way, if thread A is writing to integer variable X at the same time as thread B is reading from the same variable, is it possible that thread B could end up a few bytes of the previous value mixed in with a few bytes of the value being written? Is this architecture dependent, e.g. ok for 4 byte integers on 32 bit systems but unsafe on 8 byte integers on 64 bit systems?

Edit: Just saw this related post which helps a fair bit.

+4  A: 

There's no support for atomic variables in C++, so you do need locking. Without locking you can only speculate about what exact instructions will be used for data manipulation and whether those instructions will guarantee atomic access - that's not how you develop reliable software.

sharptooth
I have to disagree (somewhat). There are no atomic variables in c++, no, but there aren't any locks either. As soon as you get into multithreading, you have to rely on the guarantees given by your specific compiler. And *that* does have a lot of guarantees for atomicity. Generally, accesses to word-sized objects will be atomic. Of course, the C++ language doesn't guarantee this, but your specific compiler probably does. Of course, the compiler typically makes few guarantees about reordering, so you may still need locking, or at least memory barriers, depending on exactly what you're doing
jalf
+3  A: 

Yes it would be better to use synchronization. Any data accessed by multiple threads must be synchronized.

If it is windows platform you can also check here :Interlocked Variable Access.

Incognito
+1  A: 

Yes. If you are on Windows you can take a look at Interlocked functions/variables and if you are of the Boost persuasion then you can look at their implementation of atomic variables.

If boost is too heavyweight putting "atomic c++" into your favourite search engine will give you plenty of food for thought.

graham.reeds
+6  A: 

You are never locking a value - you are locking an operation ON a value.

C & C++ do not explicitly mention threads or atomic operations - so operations that look like they could or should be atomic - are not guaranteed by the language specification to be atomic.

It would admittedly be a pretty deviant compiler that managed a non atomic read on an int: If you have an operation that reads a value - theres probably no need to guard it. However- it might be non atomic if it spans a machine word boundary.

Operations as simple as m_counter++ involves a fetch, increment, and store operation - a race condition: another thread can change the value after the fetch but before the store - and hence needs to be protected by a mutex - OR find your compilers support for interlocked operations. MSVC has functions like _InterlockedIncrement() that will safely increment a memory location as long as all other writes are similarly using interlocked apis to update the memory location - which is orders of magnitude more lightweight than invoking a even a critical section.

GCC has intrinsic functions like __sync_add_and_fetch which also can be used to perform interlocked operations on machine word values.

Chris Becke
Thanks for this, InterlockedExchange is probably the function I'm looking for, as only one thread actually writes to the variable in question, whereas others simply read it.
Shane MacLaughlin
+1  A: 

In 99.99% of the cases, you must lock, even if it's access to seemingly atomic variables. Since C++ compiler is not aware of multi-threading on the language level, it can do a lot of non-trivial reorderings.

Case in point: I was bitten by a spin lock implementation where unlock is simply assigning zero to a volatile integer variable. The compiler was reordering unlock operation before the actual operation under the lock, unsurprisingly, leading to mysterious crashes.

See:

  1. Lock-Free Code: A False Sense of Security
  2. Threads Cannot be Implemented as a Library
Alex B
I am not surprised at all :) Reordering is nasty here...
Matthieu M.
Compiler BUG. volatile is an optimization boundary.
Joshua
@Joshua, Do I sense some sarcasm? If not, then no, it's not a compiler bug. Nothing prevents the compiler to reorder non-volatile red/writes around volatile ones, it only can't reorder volatile reads/writes between themselves. See this question: http://stackoverflow.com/questions/2535148/volatile-qualifier-and-compiler-reorderings
Alex B
+1  A: 

If you're on a machine with more than one core, you need to do things properly even though writes of an integer are atomic. The issues are two-fold:

  1. You need to stop the compiler from optimizing out the actual write! (Somewhat important this. ;-))
  2. You need memory barriers (not things modeled in C) to make sure the other cores take notice of the fact that you've changed things. Otherwise you'll be tangled up in caches between all the processors and other dirty details like that.

If it was just the first thing, you'd be OK with marking the variable volatile, but the second is really the killer and you will only really see the difference on a multicore machine. Which happens to be an architecture that is becoming far more common than it used to be… Oops! Time to stop being sloppy; use the correct mutex (or synchronization or whatever) code for your platform and all the details of how to make memory work like you believe it to will go away.

Donal Fellows
+1  A: 

Multithreading is hard and complex. The number of hard to diagnose problems that can come around is quite big. In particular, on intel architectures reads and writes from aligned 32bit integers is guaranteed to be atomic in the processor, but that does not mean that it is safe to do so in multithreaded environments.

Without proper guards, the compiler and/or the processor can reorder the instructions in your block of code. It can cache variables in registers and they will not be visible in other threads...

Locking is expensive, and there are different implementations of lock-less data structures to optimize for high performance, but it is hard to do it correctly. And the problem is a that concurrency bugs are usually obscure and difficult to debug.

David Rodríguez - dribeas