C++: use shared_ptr with multithread
GotW #95 Solution: Thread Safety and Synchronization
In the role of a user:
shared_ptr it self is not a fully synchronized object. When one shared_ptr is accessed in two threads and two threads just read(or calling const functions) from it, it is correctly synchronized.
In the role of class implementor:
When implementing a not fully internal synchronized class, one should make sure that :
- if the code knows and owns one shared writable object, the code is responsible to synchronized write and read operations on it. Common techniques are mutex, atomic<>
- concurrent operations on different objects must be safe. no synchronization is needed.
- concurrent const operations on same objects must be safe.
Partial internal synchronization in shared_ptr:
When it comes to partial internal synchronization, shared_ptr serves as a good example. Since the reference counter is a shared variable, it needs to be protected when concurrent write occurs. It is not possible for the calling code to know that. So shared_ptr protect the reference count in its implementation.
Shared_ptr usage example in frequently seen circumstances:
examples about whether the user code is correctly synchronized(these examples are in blog shown in the beginning of this post): 1.
// Case A: Using distinct objects
// thread 1 (performs no additional synchronization)
auto x = sp1; // read from sp1 (writes the count!)
// thread 2 (performs no additional synchronization)
sp2 = something_else; // write to sp2 (writes the count!)
Here sp1 and sp2 is in no way related. It’s correct, because ‘concurrent operations on different objects must be safe’.
2.
// Case B: const access to the same object
// thread 1 (performs no additional synchronization)
auto sp3 = sp; // read from sp (writes the count!)
// thread 2 (performs no additional synchronization)
auto sp4 = sp; // read from sp (writes the count!)
It’s correct, because ‘concurrent const operations on same objects must be safe’.
3.
// Case C: External synchronization still required as usual
// for non-const access to same visible shared object
// thread 1
{
lock_guard hold(mut_sp); // acquire lock
auto sp3 = sp; // read from sp
}
// thread 2
{
lock_guard hold(mut_sp); // acquire lock
sp = something_else; // modify sp
}
Synchronization is needed. This is the common case that one object is writable among threads.
Brief on external synchronization of shared_ptr and other not fully internal synchronized types:
So if main function initializes one shared_ptr and starts threads and threads just read from the shared_ptr, no external synchronization is needed. If the shared_ptr is modified in the future, then it is a writable shared object and external synchronization is needed.
How to determinate a class should be fully synchronized:
- It’s a writable object.
- It’s shared among threads.
Classes that meet all the requirements is usually used for either inter-thread communication or synchronization.