I wrote in "Linux multithreaded Server programming: Using the Muduo C + + network Library" section 1.9, "re-discuss shared_ptr thread safety" in the article:
(shared_ptr) The reference count itself is secure and unlocked, but the object's read-write is not because the shared_ptr has two data members and the read-write operation cannot be atomized. According to the documentation (http://www.boost.org/doc/libs/release/libs/smart_ptr/shared_ptr.htm#ThreadSafety), shared_ptr The thread-safe level is the same as the built-in type, the standard sink, the std::string, that is:
• A shared_ptr object entity can be read simultaneously by multiple threads (document example 1);
• Two shared_ptr object entities can be written by two threads at the same time (example 2), "destructor" to write operations;
• If you want to read and write the same shared_ptr object from multiple threads, you need to lock (example 3~5).
Note that the above is the thread-safe level of the shared_ptr object itself, not the thread-safe level of the object it manages.
The latter text (p.18) describes how to lock and unlock efficiently. This article specifically analyzes why " because shared_ptr has two data members, read and write operation can not be atomized" so that multithreading read and write the same shared_ptr object need to lock. It seems to me that the obvious conclusions are also questionable, which will lead to disastrous consequences and deserve my writing this article. Taking Boost::shared_ptr as an example, this paper may be slightly different from std::shared_ptr.
Data structure of shared_ptr
SHARED_PTR is a reference-counting (reference-counting) smart pointer, where almost all implementations use a count (counting) on the heap (heap) (in addition to the theoretical use of circular lists, but no instances). Specifically,,shared_ptr<foo> contains two members, one pointing to Foo's pointer ptr and the other Ref_count pointer (its type is not necessarily the original pointer, possibly the class type, but does not affect the discussion here), pointing to the ref_ on the heap The Count object. The Ref_count object has multiple members, and the concrete data structure is shown in Figure 1, where deleter and allocator are optional.
Figure 1:shared_ptr the data structure.
To simplify and highlight the focus, the following text only draws the Use_count value:
The above is shared_ptr<foo> x (new Foo); The corresponding memory data structure.
If the shared_ptr<foo> y = x is again executed; Then the corresponding data structure is as follows.
But y=x involves two members of the replication, the two-step copy will not occur at the same time (atom).
In intermediate Step 1, copy the PTR pointer:
Intermediate Step 2, copy the Ref_count pointer, causing the reference count plus 1:
The sequence of steps 1 and 2 is related to the implementation (so there is no y.ptr point in step 2), and I've seen 1 and 2 first.
Since Y=x has two steps, if there is no mutex protection, then there are race condition in multithreading.
Multithreading unprotected read-write shared_ptr may appear race condition
Consider a simple scenario with 3 shared_ptr<foo> objects x, G, N:
Shared_ptr<foo> g (new Foo); shared_ptrshared_ptr<foo> x shared between threads; The local variable shared_ptr<foo> n (new Foo) of thread A; Local Variables for thread B
At first, it's all about it.
Thread A executes x = g; (That is, read g), step 1 is completed below, and step 2 has not been performed. Then switch to the B thread.
At the same time programming B executes g = n; (That is, write g), two steps were completed together.
First Step 1:
Again, Step 2:
This is the Foo1 object has been destroyed, the X.PTR has become the empty suspension pointer!
Finally, back to thread A, complete step 2:
Multithreading read and write without protection, resulting in "x is the null pointer" consequences. This is why multithreading must be locked to read and write the same shared_ptr.
Of course, race condition is much more than that, and other threads (interweaving) may cause other errors.
Think, if shared_ptr's operator= implementation is to replicate Ref_count (step 2) and then replicate PTR (step 1), what race condition?
Miscellaneous shared_ptr as key to Unordered_map
If you put the boost::shared_ptr in the Unordered_set, or the key for unordered_map, be careful that the hash table degrades to a linked list. http://stackoverflow.com/questions/6404765/c-shared-ptr-as-unordered-sets-key/12122314#12122314
Until Boost 1.47.0 is released,unordered_set<std::shared_ptr<t> > can compile through, but its hash_value is shared_ptr implicitly converted to bool. That is, if you do not customize the hash function, then unordered_{set/map} is degraded to a linked list. https://svn.boost.org/trac/boost/ticket/5216
Boost 1.51 adds overloads to the BOOST/FUNCTIONAL/HASH/EXTENSIONS.HPP, and now it's safe and efficient to use unordered_set<std::shared_ptr as long as it contains this header file. >.
This is also the reason for the examples/idleconnection example of Muduo to define the Hash_value (const boost::shared_ptr<t>& x) function (book 7th. 10.2, p.255). This bug is the result of the boost version in Debian 6 squeeze, Ubuntu 10.04 LTS.
Why does the ref_count in Figure 1 also have pointers to Foo?
The Shared_ptr<foo> SP (new Foo) captures the destructor of Foo when the SP is constructed. In fact, shared_ptr.ptr and ref_count.ptr can be different types (as long as there is an implicit conversion between them), which is a major feature of shared_ptr. Divided by 3 points:
1. No virtual destructor is required , but bar and Foo are not virtual destructors, assuming bar is the base class for Foo.
Shared_ptr<foo> SP1 (new Foo); The type of ref_count.ptr is foo*
Shared_ptr<bar> SP2 = SP1; Can be assigned value, automatic upward transition (up-cast)
Sp1.reset (); Then the reference count for the Foo object drops to 1.
Thereafter SP2 can still safely manage the lifetime of the Foo object and release Foo safely and completely, because its Ref_count remembers the actual type of Foo.
2. shared_ptr<void> can point to and securely manage (deconstruct or prevent destructors) any object; the tie () function of the Muduo::net::channel class uses this feature to prevent premature deconstruction of objects, see book 7.15.3 Festival.
Shared_ptr<foo> SP1 (new Foo); The type of ref_count.ptr is foo*
Shared_ptr<void> SP2 = SP1; Can be assigned value, foo* to void* automatic transformation
Sp1.reset (); Then the reference count for the Foo object drops to 1.
Thereafter SP2 can still safely manage the lifetime of the Foo object and release Foo securely and completely without the void* of delete, because the delete is ref_count.ptr, not sp2.ptr.
3. Multiple inheritance. Suppose Bar is one of several base classes of Foo, then:
Shared_ptr<foo> SP1 (new Foo);
Shared_ptr<bar> SP2 = SP1; At this point sp1.ptr and Sp2.ptr may point to a different address because the Bar subobject offset in Foo object may not be 0.
Sp1.reset (); The reference count of the Foo object drops to 1 at this time
However, SP2 can still safely manage the lifetime of the Foo object and release Foo safely and completely, because the delete is not bar*, but the original foo*. In other words, sp2.ptr and ref_count.ptr may have different values (of course they are of different types).
Why try to use make_shared ()?
In order to save a memory allocation, the original shared_ptr<foo> x (new Foo); You need to allocate memory for Foo and Ref_count, and now, with make_shared (), you can allocate a large enough memory at once to shelter Foo and Ref_count objects. The data structure is:
However, the constructor parameters of Foo are passed to make_shared (), which is then passed to Foo::foo (), which can only be solved perfectly by perfect forwarding in c++11.