Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Is the default copy constructor thread-safe in c++?

class CSample{
     int a;
     // ..... lots of fields
}
Csample c;

As we know, Csample has a default copy constructor. When I do this:

Csample d = c

the default copy constructor will happen. My question is: is it thread safe? Because maybe someone modifies c in another thread when you do the copy constructor. If so, how does the compiler do it? And if not, I think it's horrible that the complier can't guarantee that the copy constructor be thread safe.

like image 538
frank.lin Avatar asked Jul 31 '14 09:07

frank.lin


2 Answers

Nothing in C++ is thread-safe¹ unless explicitly noted.

If you need to read object c while it may be modified in another thread, you are responsible for locking it. That is a general rule and there is no reason why reading it for purpose of creating a copy should be an exception.

Note, that the copy being created does not need to be locked, because no other thread knows about it yet. Only the source needs to be.

The compiler does not guarantee anything to be thread-safe on it's own, because 99.9% of things don't need to be thread-safe. Most things only need to be reentrant³. So in the rare case you actually need to make something thread-safe, you have to use locks (std::mutex) or atomic types (std::atomic<int>).

You can also simply make your objects constant and then you can read them without locking, because nothing is writing them after creation. Code using constant objects is both more easily parallelised and more easily understood in general, because there is fewer things with state you have to track.

Note that on the most common architecture the mov instruction with int operands happens to be thread-safe. On other CPU types even that might not be true. And because the compiler is allowed to preload values, integer assignment in C++ is not anyway.


¹A set of operations is considered thread-safe if calling them concurrently on the same object is well defined². In C++, calling any modifying operation and any other operation concurrently on the same object is a data race, which is UndefinedBehaviour™.

²It is important to note, that if an object is "thread-safe", it does not really help you much most of the time anyway. Because if an object guarantees that when it's concurrently written you'll always read the new or the old value (C++ allows that when an int c is being changed from 0 to 1000 by one thread, another thread may read, say, 232), most of the time that won't help you, because you need to read multiple values in a consistent state, for which you have to lock over them yourself anyway.

³Reentrant means that the same operation may be called on different objects at the same time. There are a few functions in standard C library that are not reentrant, because they use global (static) buffers or other state. Most have reentrant variants (with _r suffix, usually) and the standrd C++ library uses these, so the C++ part is generally reentrant.

like image 151
Jan Hudec Avatar answered Sep 20 '22 01:09

Jan Hudec


The general rule in the standard is simple: if an object (and sub-objects are objects) is accessed by more than one thread, and is modified by any thread, then all accesses must be synchronized. There are numerous reasons for this, but the most basic one is that protecting at the lowest level is usually the wrong level of granularity; adding synchronization primitives would only make the code run significantly slower, without any real advantage for the user, even in a multithreaded environment. Even if the copy constructor were "thread-safe", unless the object is somehow totally independent of all other context, you'll probably need some sort of synchronization primitives at a higher level.

And with regards to "thread-safety": the usual meaning among experienced practitionners it that the object/class/whatever specifies exactly how much protection it guarantees. Precisely because such low level definitions such as you (and many, many others) seem to use are useless. Synchronizing each function in a class is generally useless. (Java made the experiment, and then backed off, because the gurantees they made in the initial versions of their containers turned out to be expensive and worthless.)

like image 39
James Kanze Avatar answered Sep 18 '22 01:09

James Kanze