3

In C++, I feel like I've always been lead to believe that things like var++ and var-- are reasonably threadsafe - AKA - you have a guarantee that your value will increase or decrease at some point in time.

It was upon this belief that I built my understanding of non-blocking algorithms for thread-safe operations. Yet, today I'm in shock, as I have a variable that is not getting incremented - and am therefore questioning the validity of a large amount of my past work.

In a small program, I have a global variable initialized at 0. Eight P-Threads are started up that each call varname++ a total of 1024 times, totalling to 8*1024 increments. Yet after all threads have finished executing, the value of varname is significantly less than 8*1024.

Did I miss the boat here? Can someone please enlighten me?

masoud
  • 55,379
  • 16
  • 141
  • 208

3 Answers3

5

What exactly would lead you to the belief those where threadsafe? In general they are not. The reason for that is simple: Arithmetic is generally done on registers, so var++ might be transformed to something like the following:

load var to R1
inc R1
store R1 to var

If another thread modifies var between the load and the store you will obviously loose that update. In reality this problem will be even worse, since the compiler can decide to keep the variable in a register for as long as it wants (well for as long as it can prove that var isn't accessed through any pointers to it (in the same thread)).

Having multiple threads access the same variable is defined to be a data race (and therefore undefined behaviour) by the (C++11) standard, unless none of the thread modifies the variable (if all do read access only, you are fine).

For threadsafety operations, you need to use either locking (e.g. using std::mutex in C++11) or atomic operations. If you use C++11, you can use std::atomic<int> (or whatever type your counter is) as the type for var to get threadsafe modifications. (Arithmetic) Operations on std::atomic<T> (like the increment and decrement operators) are guaranteed to be threadsafe by the standard.

Grizzly
  • 19,595
  • 4
  • 60
  • 78
  • most people are saying 'No, ++ is not atomic.' which is obvious from my observation.-But how on earth did classic things like non-blocking ring-buffers use them as control without corruption? – user2126895 Mar 23 '13 at 22:37
  • @user2126895: Quite simply: They don't. What the do is use an atomic increment operation like the one provided by `std::atomic`. – Grizzly Mar 23 '13 at 22:39
  • I respect your perspective but algorithms without synchronization mechanisms like that do exist - specifically for single-read single-writer scenarios. I must have forgotten how they work though. – user2126895 Mar 23 '13 at 22:53
  • @user2126895: If two threads access the same variable and at least one of them does a write, such an algorithm would invoke undefined behaviour according to the standard. Therefore no correct algorithm which does that can exist in standard C++. Specifically the compiler doesn't have any obligations to reload the variable, so compiler optimization can easily put you into a situation, where your updates never make it to the consumer thread. Now one can use `volatile` to hinder the optimizer, leading to something that might mostly work in practice (which was what would be done pre C++11). – Grizzly Mar 23 '13 at 23:02
  • Note however that this only works (somewhat) if you have only one writing thread. In that case the lack of atomicity of the increment isn't to bad, since you have no concurrent updates and your writeback is typically atomic. However I still would recommend using atomics instead, since there are no guarantees for the non atomic approach to work (and `volatile` doesn't offer any good guarantees about reordering in relation to other operations, so getting that right requires either luck or an tremendous efford in defeating the optimizer). – Grizzly Mar 23 '13 at 23:08
  • I haven't disagreed with anything you've said about operations, compilers, or standards. (I was changing the topic) ^_^ Thanks for your feedback. (And with that, I take my leave.) – user2126895 Mar 23 '13 at 23:18
4

C++'s pre- and post-increment operators are not thread-safe.

Similar question here: I've heard i++ isn't thread safe, is ++i thread-safe?

Community
  • 1
  • 1
Nate Hekman
  • 6,507
  • 27
  • 30
2

Yes, you have missed something--namely that your reads and writes are not atomic. So a number of threads could read the value, then increment it, then write it back, and if those operations are all happening "in parallel", the value will only be incremented by one.

You can fix this using C++11's (or Boost's) std::atomic type wrapper, described here: http://en.cppreference.com/w/cpp/atomic/atomic

John Zwinck
  • 239,568
  • 38
  • 324
  • 436