3

I have a shared variable of type double. This variable will be accessed by two threads. One thread will ever only write the variable, whereas the other thread will ever only read the variable.

Do I still get race condition here? If yes, is there an "easy" way in C++ to implement atomic access? How do I implement it efficiently if there is going to be much much more reads than writes? Do I need to mark the variable as volatile?

EDIT: OK the "reader" thread works periodically on batches of data and the propagation of the new value is not time-sensitive. Instead of implementing complicated interlocking that I have no good way to test, I can just declare another temp variable the writer thread will write to. Then when the reader is finished with one batch, it can atomically propagate the temp value to the actual variable. Would that be race-condition-free?

Jakub Zaverka
  • 8,816
  • 3
  • 32
  • 48
  • @Jesse: Many compilers don't have a working implementation of that header yet. – Ben Voigt Apr 17 '12 at 22:38
  • A simple mutex could work here to make sure you are reading the good value. The mutex would block access to the variable when the writing thread is using the variable and when it's done unlocks it for the reading thread to do his job. On both unix and windows the implementation is quite simple. – Chris911 Apr 17 '12 at 22:38
  • @Chris911: A mutex is unnecessarily expensive and potentially blocks one thread. There's a wait-free implementation possible. – Ben Voigt Apr 17 '12 at 22:45
  • @Jakub: Your edit is not ok. There's still a chance that the reader would make a copy at exactly the same time the writer is changing it, and see a partially-updated value. – Ben Voigt Apr 17 '12 at 22:48
  • What is your architecture? The answer does depend quite a lot on what hardware you are going to use. – David Rodríguez - dribeas Apr 17 '12 at 22:48
  • @BenVoigt: I read your solution and really like it. Honestly in these situation I almost always use a mutex and make sure there's no deadlock possible. This might not be the place but care to shortly explain when your solution wouldn't work and you would have to use a mutex instead? – Chris911 Apr 17 '12 at 22:50
  • @Ben And if both write and read on the temp would be done using `InterlockedExchange`? @David Win32 (generally only desktops) – Jakub Zaverka Apr 17 '12 at 22:50
  • @Chris911: I thought I already did say that, in the final sentence. – Ben Voigt Apr 17 '12 at 22:51
  • @JakubZaverka: `InterlockedExchange` only works on pointers (or pointer-sized integers). You can't use it directly with `double` variables. – Ben Voigt Apr 17 '12 at 22:52

2 Answers2

8

Yes, there's a race condition, since double variables are not atomic on most processors.

Use 3 doubles (possibly an array with extra padding in between, to avoid false sharing that kills performance).

One is owned by the reader, one is owned by the writer, one is being handed off.

To write: write to the write slot, then atomically swap (e.g. with InterlockedExchange) the pointer/index for the write slot with the index for the handoff slot. Since the index is pointer-sized or smaller, atomically swapping is easy as long as the variable is properly aligned. If by chance your platform offers interlocked-exchange with and without memory barriers, use the one with.

To read: atomically swap the pointer/index for the read slot with the index for the handoff variable. Then read the read slot.

You should actually include a version number also, since the read thread will tend to bounce between the latest and previous slot. When reading, read both before and after the swap, then use the one with the later version.

Or, in C++11, just use std::atomic.

Warning: the above only works for single writer/single reader (the particular case in this question). If you have multiple, think about a reader-writer lock or similar protecting all access to the variable.

Ben Voigt
  • 277,958
  • 43
  • 419
  • 720
  • How would that affect performance? I am talking about reads occuring hundreds of times each second whereas writes can happen like once in an hour, if even at all. – Jakub Zaverka Apr 17 '12 at 23:05
  • @Jakub: Hundreds of times per second is nothing, you don't even need to worry about the performance. If it were hundreds of thousands of times per second, that would be time to be concerned, and I think my proposal will still be one of the fastest. – Ben Voigt Apr 17 '12 at 23:37
  • Why not just use a 64 bit union? One field is a 64 bit int, the other field is a double. You can CAS the 64 bit int all day long. You can copy the union using the 64 bit int and read the double after the copy is made. – johnnycrash May 19 '12 at 03:52
0

You might want to take a look at this that discusses reads/writes of primitive types:

Are C++ Reads and Writes of an int Atomic?

Community
  • 1
  • 1
David Z.
  • 5,621
  • 2
  • 20
  • 13
  • 1
    How's that help with `double`, which is usually not the same size as `int` and has different alignment requirements also? – Ben Voigt Apr 17 '12 at 22:36
  • I think the key takeaway that I wanted to get out of that thread is that reads and writes really depend on the underlying architecture of the system that you using (and I believe some multi-byte x64 instructions are atomic if aligned properly), the compiler (i.e. http://msdn.microsoft.com/en-us/library/aa290049%28VS.71%29.aspx), etc. As you pointed out, it's safer to assume that its not atomic and go from there. I could have made that a bit more clear in my response. – David Z. Apr 17 '12 at 22:58