shared_ptr race condition
Hi, I have studied the sp_counted_base implementation and I can't see how it is trade safe. For instance: bool add_ref_lock() // true on success { pthread_mutex_lock( &m_ ); bool r = use_count_ == 0? false: ( ++use_count_, true ); pthread_mutex_unlock( &m_ ); return r; } void release() // nothrow { pthread_mutex_lock( &m_ ); long new_use_count = --use_count_; pthread_mutex_unlock( &m_ ); if( new_use_count == 0 ) { dispose(); weak_release(); } } I suppose to exist a race condition because I can't execute the comparation "if( new_use_count) == 0" outside of critical region delimited by mutex. So if: 1. Reference count = 1 2. Thread A executes release and is interrupted after execute the comparation if( new_use_count) == 0 (the comparation returns true) 3. Thread B executes add_ref_lock, then Ref count = 1 4. Thread A executes "dispose()" and releases the pointer ! What Do you think about ? Eduardo Panisset.
on Fri Aug 15 2008, "Eduardo Panisset"
Hi,
I have studied the sp_counted_base implementation and I can't see how it is trade safe.
<snip>
1. Reference count = 1 2. Thread A executes release and is interrupted after execute the comparation if( new_use_count) == 0 (the comparation returns true) 3. Thread B executes add_ref_lock, then Ref count = 1 4. Thread A executes "dispose()" and releases the pointer !
What Do you think about ?
The premise is that if reference count = 1, only one thread can possibly be referencing this shared_count. Note that shared_ptr is only supposed to be "as threadsafe as int;" it doesn't claim that a single shared_ptr object can ever be inspected or modified while it is being modified in another thread. Yes, it's possible to be less threadsafe than int when there's hidden sharing! In that case, two threads wouldn't be able to concurrently modify/inspect *copies* of the same object (in this case, the shared_ptr). HTH, -- Dave Abrahams BoostPro Computing http://www.boostpro.com
Yes,
But then, the question is: Can I have simultaneous read/write operations on
shared_ptr ? I don´t think so, because the reasons already exposed. Right ?
Eduardo Panisset.
On Fri, Aug 15, 2008 at 8:49 PM, David Abrahams
on Fri Aug 15 2008, "Eduardo Panisset"
http://eduardo.panisset-at-gmail.com/> wrote: Hi,
I have studied the sp_counted_base implementation and I can't see how it is trade safe.
<snip>
1. Reference count = 1 2. Thread A executes release and is interrupted after execute the comparation if( new_use_count) == 0 (the comparation returns true) 3. Thread B executes add_ref_lock, then Ref count = 1 4. Thread A executes "dispose()" and releases the pointer !
What Do you think about ?
The premise is that if reference count = 1, only one thread can possibly be referencing this shared_count. Note that shared_ptr is only supposed to be "as threadsafe as int;" it doesn't claim that a single shared_ptr object can ever be inspected or modified while it is being modified in another thread.
Yes, it's possible to be less threadsafe than int when there's hidden sharing! In that case, two threads wouldn't be able to concurrently modify/inspect *copies* of the same object (in this case, the shared_ptr).
HTH,
-- Dave Abrahams BoostPro Computing http://www.boostpro.com _______________________________________________ Boost-users mailing list Boost-users@lists.boost.org http://lists.boost.org/mailman/listinfo.cgi/boost-users
on Fri Aug 15 2008, "Eduardo Panisset"
Yes,
But then, the question is: Can I have simultaneous read/write operations on shared_ptr ?
Not without additional synchronization (e.g. via a mutex with all threads cooperating by locking before accessing the shared_ptr). But do you really need to do that? Can you not give each thread that needs access a separate copy of the shared_ptr?
I don´t think so, because the reasons already exposed. Right ?
Correct. And the documentation says as much (http://www.boost.org/doc/libs/1_36_0/libs/smart_ptr/shared_ptr.htm#ThreadSaf...) so you're not really "exposing" anything that isn't already known ;-) -- Dave Abrahams BoostPro Computing http://www.boostpro.com
Excuse me for insisting on my question, But I would like to ensure that I'm understanding shared_ptr correctly. Not without additional synchronization (e.g. via a mutex with all threads cooperating by locking before accessing the shared_ptr). But do you really need to do that? Can you not give each thread that needs access a separate copy of the shared_ptr? Exactly. I give each thread a instance of shared_ptr, but the point is: the critical region of release member function does not avoid race conditions because It does not protect de code right below: if( new_use_count == 0 ) { dispose(); weak_release(); } How Can I ensure that the release member function wouldn't dispose a pointee more than one time ? (release executing concurrently from different shared_ptr instances) How Can I ensure that I wouldn't dispose a pointee used by another shared_prt in scope ? (release and add_ref_lock executing concurrently from different shared_ptr instances). In my opinion the release member function implementation should be something like below: (So I could avoid the race conditions among several shared_ptr instance sharing a commom pointee) void release() // nothrow { pthread_mutex_lock( &m_ ); if (--use_count == 0) { dispose(); weak_release(); } pthread_mutex_unlock( &m_ ); } Eduardo Panisset.
on Sat Aug 16 2008, "Eduardo Panisset"
Excuse me for insisting on my question,
But I would like to ensure that I'm understanding shared_ptr correctly.
Okay, but please use standard quoting so threading is preserved:
Not without additional synchronization (e.g. via a mutex with all threads cooperating by locking before accessing the shared_ptr). But do you really need to do that? Can you not give each thread that needs access a separate copy of the shared_ptr?
Exactly. I give each thread a instance of shared_ptr, but the point is: the critical region of release member function does not avoid race conditions because It does not protect de code right below:
if( new_use_count == 0 ) { dispose(); weak_release(); }
How Can I ensure that the release member function wouldn't dispose a pointee more than one time ? (release executing concurrently from different shared_ptr instances)
Because the reference count was 1, there can be only one shared_ptr instance that owns the pointee. No other threads can be referencing it. [In fact it's very likely that no mutex is used on any given platform -- atomic reference counting is used where available.] -- Dave Abrahams BoostPro Computing http://www.boostpro.com
David Abrahams wrote:
on Sat Aug 16 2008, "Eduardo Panisset"
wrote: if( new_use_count == 0 ) { dispose(); weak_release(); }
How Can I ensure that the release member function wouldn't dispose a pointee more than one time ? (release executing concurrently from different shared_ptr instances)
Because the reference count was 1, there can be only one shared_ptr instance that owns the pointee. No other threads can be referencing it.
But since the mutex is released, a copy could be created before the new_use_count test is reached? Or is it impossible to create a copy if use_count_ is zero? [Sorry, I didn't take the time to look at the code myself, gotta run.] --> Mika Heiskanen
Mika Heiskanen wrote:
David Abrahams wrote:
on Sat Aug 16 2008, "Eduardo Panisset"
wrote: if( new_use_count == 0 ) { dispose(); weak_release(); }
How Can I ensure that the release member function wouldn't dispose a pointee more than one time ? (release executing concurrently from different shared_ptr instances)
Because the reference count was 1, there can be only one shared_ptr instance that owns the pointee. No other threads can be referencing it.
But since the mutex is released, a copy could be created before the new_use_count test is reached?
No, because since the old reference count was 1, only the current thread has a copy, and since the thread is busy executing this code, it can't also be making a copy to give to another thread. Or is it impossible to create a copy if
use_count_ is zero?
Yes, because we're still inside the sp_counted_base::release() function. -- Jon Biggar Floorboard Software jon@floorboard.com jon@biggar.org
on Sat Aug 16 2008, Mika Heiskanen
David Abrahams wrote:
on Sat Aug 16 2008, "Eduardo Panisset"
wrote: if( new_use_count == 0 ) { dispose(); weak_release(); } How Can I ensure that the release member function wouldn't dispose a pointee more than one time ? (release executing concurrently from different shared_ptr instances)
Because the reference count was 1, there can be only one shared_ptr instance that owns the pointee. No other threads can be referencing it.
But since the mutex is released, a copy could be created before the new_use_count test is reached? Or is it impossible to create a copy if use_count_ is zero?
What thread can create a copy? We know exactly what the only thread that has a reference to the owned object is doing (it's executing the very code we're concerned with). -- Dave Abrahams BoostPro Computing http://www.boostpro.com
On Sat, Aug 16, 2008 at 5:42 PM, David Abrahams
on Sat Aug 16 2008, Mika Heiskanen
http://mika.heiskanen-at-fmi.fi/> wrote: David Abrahams wrote:
on Sat Aug 16 2008, "Eduardo Panisset"
http://eduardo.panisset-at-gmail.com/> wrote: if( new_use_count == 0 ) { dispose(); weak_release(); } How Can I ensure that the release member function wouldn't dispose a pointee more than one time ? (release executing concurrently from different shared_ptr instances)
Because the reference count was 1, there can be only one shared_ptr instance that owns the pointee. No other threads can be referencing it.
But since the mutex is released, a copy could be created before the new_use_count test is reached? Or is it impossible to create a copy if use_count_ is zero?
What thread can create a copy? We know exactly what the only thread that has a reference to the owned object is doing (it's executing the very code we're concerned with).
Ok, I agree with that. I increment the reference count by copying a shared_ptr to another shared_ptr (before the shared_ptr releases its pointee). However, let me give another example: There are two threads, each one with its own shared_ptr. Thread 2 created its shared_ptr2 by copying the shared_prt1 of thread 1. Then the reference count is equal to 2. Some time later, shared_ptr1 goes out of scope and then shared_prt2 also goes out of scope. Consider the following event sequence: 1. Thread 1 executes release member function, and it is preempted before executing comparation if (new_use_count == 0) 2. Thread 2 executes release member function until the end, disposing the pointee. 3. Thread 1 is rescheduled, executes comparation if (new_use_count == 0) and also diposes the pointee! Is it possible, isn't it?
on Sat Aug 16 2008, "Eduardo Panisset"
There are two threads, each one with its own shared_ptr. Thread 2 created its shared_ptr2 by copying the shared_prt1 of thread 1. Then the reference count is equal to 2. Some time later, shared_ptr1 goes out of scope and then shared_prt2 also goes out of scope. Consider the following event sequence:
1. Thread 1 executes release member function, and it is preempted before executing comparation if (new_use_count == 0) 2. Thread 2 executes release member function until the end, disposing the pointee. 3. Thread 1 is rescheduled, executes comparation if (new_use_count == 0) and also diposes the pointee!
Is it possible, isn't it?
No. Thread 1 will see new_use_count == 1, thread 2 will see new_use_count == 0 and dispose the pointee, and thread 1 will continue off the end of the function without disposing it. -- Dave Abrahams BoostPro Computing http://www.boostpro.com
On Sat, Aug 16, 2008 at 9:32 PM, David Abrahams
on Sat Aug 16 2008, "Eduardo Panisset"
http://eduardo.panisset-at-gmail.com/> wrote: There are two threads, each one with its own shared_ptr. Thread 2 created its shared_ptr2 by copying the shared_prt1 of thread 1. Then the reference count is equal to 2. Some time later, shared_ptr1 goes out of scope and then shared_prt2 also goes out of scope. Consider the following event sequence:
1. Thread 1 executes release member function, and it is preempted before executing comparation if (new_use_count == 0) 2. Thread 2 executes release member function until the end, disposing the pointee. 3. Thread 1 is rescheduled, executes comparation if (new_use_count == 0) and also diposes the pointee!
Is it possible, isn't it?
No. Thread 1 will see new_use_count == 1, thread 2 will see new_use_count == 0 and dispose the pointee, and thread 1 will continue off the end of the function without disposing it.
Ok, David !!! new_use_count is a local variable that receives the ref count inside the critical region synchronizing the shared variable use_count_, so there is no race condition. There is still a doubt left. How can I copy a shared_ptr from a different thread that has created it and guarantee that the pointee did not go away (race condition between function members release and add_ref_lock ) ? Please take a look: 1. Thread 2 starts copying shared_ptr already held by Thread 1 2. Before Thread 2 enters into critical region of member function add_ref_lock, it is preempted 3. Thread 1 releases its shared_ptr, disposing the pointee 4. Thread 2 is rescheduled and increments the ref count, however its pointee was disposed by Thread 1 (Item 3) One example that could generate the situation above: I could have one thread calling a clear method of std::vector, while another thread was copying a shared_ptr placed in the same std::vector. Thanks!
On Sat, Aug 16, 2008 at 10:54 PM, Eduardo Panisset
On Sat, Aug 16, 2008 at 9:32 PM, David Abrahams
wrote:
[snip]
I could have one thread calling a clear method of std::vector, while another thread was copying a shared_ptr placed in the same std::vector.
That would mean you would be sharing the std::vector, which is usually not allowed. You should synchronize the std::vector.
Thanks!
-- Felipe Magno de Almeida
On Sun, Aug 17, 2008 at 12:04 AM, Felipe Magno de Almeida < felipe.m.almeida@gmail.com> wrote:
On Sat, Aug 16, 2008 at 10:54 PM, Eduardo Panisset
wrote: On Sat, Aug 16, 2008 at 9:32 PM, David Abrahams
wrote: [snip]
I could have one thread calling a clear method of std::vector, while another thread was copying a shared_ptr placed in the same std::vector.
That would mean you would be sharing the std::vector, which is usually not allowed. You should synchronize the std::vector.
Ok Felipe, Thank you !
on Sat Aug 16 2008, "Eduardo Panisset"
On Sat, Aug 16, 2008 at 9:32 PM, David Abrahams
wrote: on Sat Aug 16 2008, "Eduardo Panisset"
wrote: > There are two threads, each one with its own shared_ptr. Thread 2 created > its shared_ptr2 by copying the shared_prt1 of thread 1. Then the reference > count is equal to 2. > Some time later, shared_ptr1 goes out of scope and then shared_prt2 also > goes out of scope. > Consider the following event sequence: > > 1. Thread 1 executes release member function, and it is preempted before > executing comparation if (new_use_count == 0) > 2. Thread 2 executes release member function until the end, disposing the > pointee. > 3. Thread 1 is rescheduled, executes comparation if (new_use_count == 0) and > also diposes the pointee! > > Is it possible, isn't it?
No. Thread 1 will see new_use_count == 1, thread 2 will see new_use_count == 0 and dispose the pointee, and thread 1 will continue off the end of the function without disposing it.
Ok, David !!! new_use_count is a local variable that receives the ref count inside the critical region synchronizing the shared variable use_count_, so there is no race condition.
There is still a doubt left. How can I copy a shared_ptr from a different thread that has created it and guarantee that the pointee did not go away (race condition between function members release and add_ref_lock ) ?
You make the copy of the shared_ptr in the first thread: void thread2(shared_ptr<X> q) { ... } void main_thread(shared_ptr<X> p) { boost::thread t( boost::bind(thread2, p) // <= _copies_ p into the thread function object ); } -- Dave Abrahams BoostPro Computing http://www.boostpro.com
On Sun, Aug 17, 2008 at 1:25 AM, David Abrahams
on Sat Aug 16 2008, "Eduardo Panisset"
http://eduardo.panisset-at-gmail.com/> wrote: On Sat, Aug 16, 2008 at 9:32 PM, David Abrahams
wrote: on Sat Aug 16 2008, "Eduardo Panisset" < eduardo.panisset-AT-gmail.com http://eduardo.panisset-at-gmail.com/> wrote:
> There are two threads, each one with its own shared_ptr. Thread 2 created > its shared_ptr2 by copying the shared_prt1 of thread 1. Then the reference > count is equal to 2. > Some time later, shared_ptr1 goes out of scope and then shared_prt2 also > goes out of scope. > Consider the following event sequence: > > 1. Thread 1 executes release member function, and it is preempted before > executing comparation if (new_use_count == 0) > 2. Thread 2 executes release member function until the end, disposing the > pointee. > 3. Thread 1 is rescheduled, executes comparation if (new_use_count == 0) and > also diposes the pointee! > > Is it possible, isn't it?
No. Thread 1 will see new_use_count == 1, thread 2 will see new_use_count == 0 and dispose the pointee, and thread 1 will continue off the end of the function without disposing it.
Ok, David !!! new_use_count is a local variable that receives the ref count inside the critical region synchronizing the shared variable use_count_, so there is no race condition.
There is still a doubt left. How can I copy a shared_ptr from a different thread that has created it and guarantee that the pointee did not go away (race condition between function members release and add_ref_lock ) ?
You make the copy of the shared_ptr in the first thread:
void thread2(shared_ptr<X> q) { ... }
void main_thread(shared_ptr<X> p) { boost::thread t( boost::bind(thread2, p) // <= _copies_ p into the thread function object ); }
Ok David, I understand now. Thank you, very much !
participants (5)
-
David Abrahams
-
Eduardo Panisset
-
Felipe Magno de Almeida
-
Jonathan Biggar
-
Mika Heiskanen