3

How do you ensure newly constructed immutable objects are safely shareable amongst threads in C++? Does the C++ memory model provide guarantees about the operations of constructors?

When you have multiple threads that share access to an object, and the object is modified, race hazards are possible. Those problems can be avoided by safely publishing the object to all threads (including all possible future threads), so all subsequent accesses to the object from any thread see the same object state, then refraining from modifying the object. Subsequent accesses to the object will be immune to race hazards without using locks (mutexes). In the extreme case, the object is immutable: once constructed, it is never changed. There is therefore an idiom of using immutable objects as much as possible in multi-threaded programs.

That still leaves the need to safely publishing the object after code in the constructor has executed. The code executed by the constructor assigns values to memory locations, but those written values might (initially) exist only in the local cache of the CPU, for example. Other threads that accessed those memory locations might see old values that were recorded at those memory locations (such as a pattern of 0x00 bytes set up by malloc, for example). There must be a mechanism to flush the local cache and invalidate the caches of the other CPUS, for the memory locations covered by the newly constructed object.

When programming a high level portable programming language, like C++, you do not concern yourself with the details of caches and cache flushing. Instead the language provides a set of guarantees (the memory model), and you have to write your code according to some idioms to reliably achieve your objective.

In Java, this is automatically done by following some rules in your class design (to over simplify: make everything final, which is somewhat like const in C++), which the Java memory model guarantees will result in the desired effect. This might be implemented by having a memory-barrier immediately after executing the code of the constructor.

How is this done in C++11? Does the C++ memory model provide guarantees about the operations of constructors, which enable you to automatically publish a newly constructed object? If so, what are the rules for your classes? And if not, and you have to add a memory barrier yourself (as you apparently have to do for .Net), is there is a idiom for efficiently publishing the object after construction?

Or does C++11 not provide thread-safe lock-free access to immutable objects? Must you guard all access to shared objects (immutable or not) with mutexes?

Raedwald
  • 46,613
  • 43
  • 151
  • 237
  • *Probably* relevant reading: http://en.cppreference.com/w/cpp/atomic/memory_order – Jesper Juhl Nov 23 '17 at 16:30
  • 2
    C++ doesn't allow object modification with simultaneous non-synchronised access to the same object from a different thread. Everything else is fair game. If no one changes the object, everyone is safe to access it. Obviously while the object is being constructed, accessing it from a different thread is a no-no. – n. m. could be an AI Nov 23 '17 at 16:35
  • related to [this question](https://softwareengineering.stackexchange.com/q/171253/56533) – ldgorman Nov 23 '17 at 16:39
  • @n.m. What do you mean by "doesn't allow" - as far as I know there is no language mechanism that prevents you from doing it - it is simply undefined behaviour for reading thread – R2RT Nov 23 '17 at 16:46
  • @R2RT it means that the behaviour of a program that does this is undefined. What else could it mean? – n. m. could be an AI Nov 23 '17 at 16:48
  • @n.m. Ye, I've posted my comment too fast and updated it. My point is your statements sounds as if C++ itself can stop you (detect it, throw an exception etc.) from doing simultanous reading and writing, which would mean it has synchronization built in. And we both know it's not true. – R2RT Nov 23 '17 at 16:52
  • @R2RT That would be 'C++ prevents sumultaneous access'. I didn't say that. – n. m. could be an AI Nov 23 '17 at 16:56
  • That's not how cache works. The cpu's will take care of cache consistency. What the user needs to be concerned with is non-atomic access on read-modify-write sequences. C++ and STL have numerous mechanisms for handling shared access. This question is too broad. – Jive Dadson Nov 23 '17 at 18:28
  • I do not believe the essence of this question, "Does the C++ memory model provide guarantees about the operations of constructors? " is too broad. – Raedwald Nov 24 '17 at 13:08

1 Answers1

0

There are two main cases:

  1. The creation of the reading thread is ordered-after the creation of the object
  2. The creation of the reading thread is not ordered-after the creation of the object

In case 1, read access to the object is automatically safe because the read in the thread is ordered-after the creation of the thread itself.

In case 2, you must order the read somehow, exactly because the thread creation doesn't provide the order. And it's fairly obvious that if the read isn't ordered-after the creation of the object, then things go wrong.

Your question wonders about details like CPU caches. That's a concern for compiler writers. You just need to obey the C++ ordering rules.

MSalters
  • 173,980
  • 10
  • 155
  • 350
  • "In case 2, you must order the read somehow". Yes, but *how* do you do that? Not in the general case, but in the specific case of a newly constructed object? "You just need to obey the C++ ordering rules" yes, but what are the *relevant* rules here? – Raedwald Nov 23 '17 at 16:48
  • @Raedwald: There are multiple mechanisms in C++ that cause ordering relations, and they can be combined (this ordering is transitive, so A comes-before B and B comes-before C means that A comes-before C). This gives you quite a few options. E.g. `std::mutex` and `std::condition_variable` both provide cross-thread ordering guarantees. But a C++ mutex has no thread preference. It often is used only to prevent "happens-concurrently" without caring which thread runs first, e.g. when you have multiple worker threads adding values to a `vector`. – MSalters Nov 23 '17 at 16:56
  • So, are you saying that constructing an object does not introduce any read ordering (does not automatically insert a memory barrier), so you must *manually* follow construction of the object with an operation that introduces a read ordering (such as acquiring and releasing a mutex)? – Raedwald Nov 23 '17 at 17:06
  • 1
    @Raedwald Object construction is not special in any way as far as threads are concerned. It's just a series of accesses. – n. m. could be an AI Nov 23 '17 at 17:06
  • "Object construction is not special in any way" is the crucial thing. – Raedwald Nov 23 '17 at 17:08
  • BTW the term used in the standard is _happens after_ – Passer By Nov 23 '17 at 17:18