shared_ptr Thread Security Comprehensive analysis _c language

Source: Internet
Author: User
Tags comments volatile

As the "STL source analysis," said, "before the source code, no secret." Based on the source code of SHARED_PTR, this paper extracts the class diagram and object graph of shared_ptr, and then analyzes how shared_ptr guarantees the thread security claimed by the document. The analysis of this article is based on the boost 1.52 version, the compiler is VC 2010.

Thread Security for shared_ptr
Boost official documentation for SHARED_PTR thread security is that the Shared_ptr object provides thread security at the same level as the built-in type. "Shared_ptrobjects offer the same level of thread safety as built-in types." Specifically the following three points.

1. The same shared_ptr object can be read simultaneously by multithreading. "A shared_ptrinstance can be" read "(accessed using only const operations) simultaneously by multiple threads."

2. Different shared_ptr objects can be modified simultaneously by multithreading (even if these shared_ptr objects manage pointers to the same object). "Different shared_ptr instances can be" written to "(accessed using mutable operations as such or reset) sim Ultaneouslyby Multiple threads (even when these instances are, and copies the share Count samereference.) "

3. The results of any other concurrent access are undefined. "Any other simultaneous accesses result in undefined behavior."

The first is the concurrent reading of objects, which is naturally thread-safe .

In the second case , if two shared_ptr objects A and B manage pointers to different objects, the two objects are completely unrelated, and supporting concurrent writing is also easy to understand. But if A and B manage pointers to the same object P, a and b need to maintain a shared memory area that records the current reference count of the P pointer. Concurrent writing of A and b necessarily involves concurrent modifications to the reference count memory area, which requires boost to do additional work, which is also the focus of this article.

In addition, weak_ptr and shared_ptr are closely related, the user can construct the shared_ptr from weak_ptr, and can construct shared_ptr from weak_ptr, but weak_ptr does not involve the life cycle of the object. Because the thread security of shared_ptr is coupled with weak_ptr, the analysis of this paper also involves weak_ptr.

Let's look at the implementation of shared_ptr and weak_ptr in general.

SHARED_PTR's structure diagram
The following is a class diagram of shared_ptr and weak_ptr extracted from the boost source code.




We first ignore the weak_ptr part of the dotted box. The highest level of shared_ptr is the user's direct use of the class, which provides shared_ptr construction, replication, reset (reset function), dereference, comparison, implicit conversion to bool and other functions. It contains a pointer to the managed object that implements the dereference operation and combines a Shared_count object to manipulate the reference count.

But the Shared_count class is not a reference counting class, it simply contains a pointer to the reference count class Sp_counted_base, which is functionally encapsulated in the sp_counted_base operation. Shared_count object creation, copying, and deletion operations, including additions to the sp_counted_base and reduction of reference counts.

The last Sp_counted_base class saves the reference count and provides no lock protection for reference-counting segments. It also contains a pointer to the managed object that is used to delete the managed object. Sp_counted_base has three derived classes that handle the user-specified deleter and allocator:

1. Sp_counted_impl_p: The user did not specify deleter and allocator

2. SP_COUNTED_IMPL_PD: User specified deleter, no allocator specified

3. SP_COUNTED_IMPL_PDA: The user has specified deleter and allocator

When you create the first shared_ptr object of pointer p, the child object Shared_count is created at the same time, Shared_count creates a specific Sp_counted_base derived class object x based on the user-supplied parameter selection. All of the shared_ptr objects that were created after the admin p all point to this unique x.

Then look at the weak_ptr in the dotted box. Weak_ptr and shared_ptr are basically similar, except that weak_ptr contains Weak_count child objects, but Weak_count and Shared_count also point to sp_counted_base.

If the above text is not clear enough, the following code will explain the problem.

Copy Code code as follows:

Shared_ptr<someobject> SP1 (New Someobject ());

Shared_ptr<someobject> Sp2=sp1;

Weak_ptr<someobject> Wp1=sp1;


When the above code is executed, the following object instances are created in memory, where a red arrow represents a pointer to a reference count object, and a black arrow represents a pointer to the managed object.




It is clear from the above that SP1, SP2, and WP1 point to the same Sp_counted_impl_p object, which holds the reference count and is the memory area that three objects, sp_counted_impl_p, SP1, and SP2, operate together. Multithreading concurrently modifies SP1, SP2, and WP1, and only sp_counted_impl_p objects are concurrently modified, so sp_counted_impl_p thread security is a key issue for shared_ptr and weak_ptr thread security. The sp_counted_impl_p thread security is implemented in its base class Sp_counted_base. The following focuses on analyzing the Sp_counted_base code.

Reference Count Class Sp_counted_base
Fortunately, Sp_counted_base's code is small, and the following text is listed in full, with comments added.

Copy Code code as follows:

Class Sp_counted_base
{
Private
Prohibit copying
Sp_counted_base (sp_counted_base const &);
Sp_counted_base & Operator= (Sp_counted_baseconst &);

Number of shared_ptr
Long Use_count_;
Number of weak_ptr +1
Long Weak_count_;

Public
Only one constructor, note that this place has two counts of 1
Sp_counted_base (): Use_count_ (1), Weak_count_ (1) {}

Virtual base class, so it can be used as a base class
Virtual ~sp_counted_base () {}

Subclasses require overloading, delete managed objects with operator delete or deleter
virtual void Dispose () = 0;

Subclasses can overload, delete the current object with allocator, etc.
virtual void Destroy () {
Delete this;
}

virtual void * Get_deleter (sp_typeinfo const & TI) = 0;

This function is used when copying shared_count according to Shared_count.
Since a shared_count is present as a source, it is recorded as a, so long as A is not released,
Use_count_ will not be release by another thread () of 1.
In addition, if a thread takes a as the source of replication and another thread releases a, the execution result is undefined.
void Add_ref_copy () {
_interlockedincrement (&AMP;USE_COUNT_);
}

This function is used when constructing shared_count according to Weak_count.
This is to avoid increasing the reference count by Weak_count,
Another thread called the release function, clearing 0 use_count_ and releasing the object to which it was directed
BOOL Add_ref_lock () {
for (;;)
{
Long TMP = static_cast< Long const volatile& > (USE_COUNT_);
if (TMP = = 0) return false;

if (_interlockedcompareexchange (&AMP;USE_COUNT_, TMP + 1, tmp) = = tmp) return true;
}
}

void Release () {
if (_interlockeddecrement (&use_count_) = = 0)
{
When the use_count_ changed from 1 to 0,
1. Releasing objects
2. Performs a decrement operation on the Weak_count_. This is because at the time of initialization (Use_count_ from 0 to 1 o'clock), the Weak_count initial value is 1
Dispose ();
Weak_release ();
}
}

void Weak_add_ref () {
_interlockedincrement (&AMP;WEAK_COUNT_);
}

Decrements the Weak_count_; and when Weak_count is 0, remove yourself.
void Weak_release () {
if (_interlockeddecrement (&weak_count_) = = 0)
{
Destroy ();
}
}

Returns the reference count. Note If the user does not have an extra lock, the reference count may be modified by another thread at the same time.
Long Use_count () const{
Return Static_cast<long const volatile &> (USE_COUNT_);
}
};

The comments in the code already illustrate some of the issues, and here's one more thing: the Use_count_ field equals the number of current shared_ptr objects, and the Weak_count_ field equals the number of current weak_ptr objects plus 1.

First of all, do not consider the situation of weak_ptr. Based on code analysis for the shared_ptr class (code is not listed but easy to find), replication between shared_ptr is done by invoking the add_ref_copy and release functions. assuming that two threads operate on SP1 and SP2 separately, the process of operation is nothing more than the following three cases:

1. SP1 and SP2 both increment reference counts, that is, Add_ref_copy is executed concurrently by concurrent invocations, that is, two _interlockedincrement (&use_count_), which is thread-safe.

2. SP1 and SP2 both decrement the reference count, that is, the release is concurrently invoked, that is, _interlockeddecrement (&use_count_) concurrent execution, which is also thread-safe. Only the thread that executes later is responsible for deleting the object.

3. SP1 Increment reference count, call ADD_REF_COPY;SP2 Decrement reference count, call release. Because of the presence of SP1, the release operation of SP2 does not in any way cause Use_count_ to become 0, meaning that the body of the IF statement in release will never be executed. Thus, this situation is still thread-safe for concurrent execution of the simplification of _interlockedincrement (&use_count_) and _interlockeddecrement (&use_count_).

Then consider weak_ptr. If the operation between weak_ptr, or from shared_ptr constructs weak_ptr, does not involve use_count_ operation, only needs to call Weak_add_ref and weak_release to operate Weak_count_. As with the analysis above, _interlockedincrement and _interlockeddecrement ensure the thread safety of WEAK_ADD_REF and weak_release concurrency operations. However, if you have an operation that constructs shared_ptr from weak_ptr, you need to consider the situation where the managed object has been freed by another thread during the construction of weak_ptr. The following error conditions may occur if the shared_ptr from the weak_ptr construct is still done through the Add_ref_copy function:


Thread 1, creating shared_ptr from weak_ptr

Thread 2, releasing the current only existing shared_ptr

1

The Judge Use_count_ is greater than 0 and waits for execution add_ref_copy

2

Call Release,use_count--。 Use_count is found to be 0, delete managed objects

3

Start execution add_ref_copy, causing Use_count to increment.

An error occurred, use_count==1, but the object has been deleted


We will naturally think that thread 1 after the end of the third line, and then judge whether the Use_count is 1, if it is 1, that the object has been deleted, judge failure is not enough. In fact, this is not workable, the following is a counter example.

Thread 1, creating shared_ptr from weak_ptr

Thread 2, releasing the current only existing shared_ptr

Thread 3, creating shared_ptr from weak_ptr

1

The Judge Use_count_ is greater than 0 and waits for execution add_ref_copy

2

The Judge Use_count_ is greater than 0 and waits for execution add_ref_copy

3

Call Release,use_count--。 Use_count is found to be 0, delete managed objects

4

Start execution add_ref_copy, causing Use_count to increment.

5

Perform add_ref_copy, resulting in use_count increments.

6

Found Use_count_!= 1, to judge the execution success.

An error occurred, use_count==2, but the object has been deleted

Found Use_count_!= 1, to judge the execution success.

An error occurred, use_count==2, but the object has been deleted


In fact, boost from WEAK_PTR constructs shared_ptr not call add_ref_copy, but calls Add_ref_lock functions. Add_ref_lock is a typical code that does not have a lock to modify a shared variable, and then copy its code again and add a proof note.

Copy Code code as follows:

BOOL Add_ref_lock () {

for (;;)

{

The first step is to record the Use_count_

Long TMP = static_cast< Long const volatile& > (USE_COUNT_);

In the second step, if the other thread has been preempted by 0, the managed object has been or is about to be freed, returning false

if (TMP = = 0) return false;

In the third step, if the IF condition is executed successfully,

Note Before modifying Use_count_, Use_count is still tmp, greater than 0

That is to say, use_count_ between the first and third steps, has never changed to 0.

This is because once Use_count becomes 0, it is impossible to add up to more than 0 again.

Therefore, between the first and third steps, the managed object cannot be freed, and returns True.

if (_interlockedcompareexchange (&USE_COUNT_, TMP + 1, tmp) = = tmp) return true;

}

}


In the note above, an inconclusive conclusion is used, "once the Use_count becomes 0, it is impossible to add up to more than 0 again." The following four can prove it.

1.Use_count_ is a private object of the Sp_counted_base class, and Sp_counted_base does not have a friend function, so Use_count_ is not modified by code outside the object.

2. member function add_ref_copy can increment use_count_, but all calls to the Add_ref_copy function are performed through a shared_ptr object. Since there are shared_ptr objects, Use_count must not be 0 before incrementing.

3. member function Add_ref_lock can increment use_count_, but as the Add_ref_lock code shows, when performing the third step, TMP is greater than 0, so Add_ref_lock does not cause Use_count_ Increments from 0 to 1

4. other member functions are never incremented use_count_

At this point, we can drop the heart, as long as Add_ref_lock returns True, incrementing the reference count is successful. Therefore, the behavior of the shared_ptr from the weak_ptr construct is also completely determined, either Add_ref_lock returns True, the construct succeeds, or the Add_ref_lock returns false, and the construction fails.

To sum up, multithreading through different shared_ptr or weak_ptr objects concurrent modification of the same reference count object Sp_counted_base is thread-safe. The Sp_counted_base object is the shared memory area where these smart pointers are uniquely manipulated, so the end result is thread-safe.

Other actions
As we analyzed earlier, different shared_ptr objects can be modified by multithreading at the same time. What about the other questions, the same shared_ptr object can be modified at the same time for multithreading? We have to note that all of the previous synchronizations are done against the reference count class Sp_counted_base, and the shared_ptr itself does not have any synchronous protection. Let's look at the non-threading-safe example shown in the boost document below

Copy Code code as follows:

Thread A
P3.reset (new int (1));

Thread B
P3.reset (new int (2)); Undefined, multiple writes


The following is the code related to the Shared_ptr class
Copy Code code as follows:

Template<class y>

void Reset (Y * p)

{
This_type (P). Swap (*this);
}

void Swap (shared_ptr<t> & other)

{
Std::swap (px, other.px);
Pn.swap (OTHER.PN);
}


As you can see, Reset performs two actions that modify the member variables, and the results of thread A and thread B may be illegal.

But in the semantics of the built-in objects, boost provides several atomic functions that support the simultaneous modification of the same shared_ptr object through these functions. This includes Atomic_store, Atomic_exchange, Atomic_compare_exchange and so on. Here is the code for implementation, no more detailed analysis.

Copy Code code as follows:

Template<class t>
void Atomic_store (Shared_ptr<t> * p, shared_ptr<t> r) {
Boost::d Etail::spinlock_pool<2>::scoped_lock lock (p);
P->swap (R);
}

Template<class t>
Shared_ptr<t> atomic_exchange (shared_ptr<t> * p, shared_ptr<t> r) {
Boost::d Etail::spinlock & sp = boost::d etail::spinlock_pool<2>::spinlock_for (P);

Sp.lock ();
P->swap (R);
Sp.unlock ();

return R;
}

Template<class t>
BOOL Atomic_compare_exchange (shared_ptr<t> * p, shared_ptr<t> * V, shared_ptr<t> W) {

Boost::d Etail::spinlock & sp = boost::d etail::spinlock_pool<2>::spinlock_for (P);
Sp.lock ();
if (P-&GT;_INTERNAL_EQUIV (*v)) {
P->swap (w);
Sp.unlock ();
return true;
}
else{
shared_ptr<t> tmp (*P);
Sp.unlock ();
Tmp.swap (*V);
return false;
}
}

Summary
As the boost document claims, Boost provides shared_ptr with thread security at the same level as the built-in type. This includes:

1. The same shared_ptr object can be read simultaneously by multithreading.

2. Different shared_ptr objects can be modified by multithreading at the same time.

3. The same shared_ptr object cannot be modified directly by multithreading, but it can be done by atomic functions.

If you replace the words "shared_ptr" in the above statement with the "built-in type", it is completely true.

Finally, I also found that some of the key points are difficult to articulate, which is also because thread security itself is more difficult to prove strictly. If you want to fully understand, it is recommended to read shared_ptr complete code.

Contact Us

The content source of this page is from Internet, which doesn't represent Alibaba Cloud's opinion; products and services mentioned on that page don't have any relationship with Alibaba Cloud. If the content of the page makes you feel confusing, please write us an email, we will handle the problem within 5 days after receiving your email.

If you find any instances of plagiarism from the community, please send an email to: info-contact@alibabacloud.com and provide relevant evidence. A staff member will contact you within 5 working days.

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.