Refcounting smart pointer?
Denis Koroskin
2korden at gmail.com
Sat Sep 6 01:50:47 PDT 2008
On Sat, 06 Sep 2008 05:44:50 +0400, Jb <jb at nowhere.com> wrote:
>
> "Sönke Ludwig" <ludwig_no at spam_informatik.uni-luebeck.de> wrote in
> message
> news:g9rt39$4qn$1 at digitalmars.com...
>> Jb schrieb:
>>> "Sönke Ludwig" <ludwig_no at spam_informatik.uni-luebeck.de> wrote in
>>> message news:g9nvrs$284k$1 at digitalmars.com...
>>>> On the other side, making a completely thread-safe variant is not
>>>> possible as far as I see, since the copy operation is not safe - so
>>>> only
>>>> the reference counting part can really be made safe.
>>>
>>> Thread safe ref counting can be done with
>>>
>>> lock inc [this.refcount]
>>> lock dec [this.refcount]
>>>
>>> You need the lock prefix or else they are not atomic.
>>>
>>>
>>>
>>>
>>
>> The problem that remains is that the copy operation (blit) of the
>> counted
>> reference struct is not synchronized or executed atomically together
>> with
>> the subsequent increment. Consider the following setup:
>>
>> struct RefCounter {
>> int* count;
>> this(){ count = new int; *count = 1; }
>> this(this){ atomic_inc(count); }
>> ~this(){ if( !atomic_dec(count) ) delete count; }
>> }
>>
>> // init
>> RefCounter a;
>>
>> // thread a
>> {
>> // write to a
>> RefCounter dummy1;
>> a = dummy1;
>> // 1. read tmp = a.count
>> // 2. atomically decrement *tmp
>> // 3. delete a.count if <= 0
>> // 4. copy dummy1 to a
>> // 5. read a.count
>> // 6. atomically increment *a.count
>> }
>>
>> // thread b
>> {
>> // read from a
>> RefCounter dummy2 = a;
>> // - copy
>> // 1. copy a to dummy
>> // 2. read dummy2.count
>> // 3. atomically increment *dummy2.count
>> }
>>
>> A possible order of execution would be:
>>
>> B 1. dummy2.count is the original a.count
>> A 1. tmp is a.count
>> A 2. *tmp/*a.count is decremented -> *a.count == 0
>> A 3. a.count is deleted
>> B 2. tmp is dummy2.count, which is the original a.count
>> B 3. *tmp/*dummy2.count/*a.count is incremented (CRASH)
>> ...
>>
>> And if there are two pointers inside of the struct, the number
>> of possible failures increases considerably. So thread-safety can only
>> be
>> guaranteed if there are no concurrent read/write or write/write accesses
>> to the same reference.
>
> Ah yeah I see the problem now.
>
>
>
The same problem exists in C++:
// global data
RefCounter globalRC;
// thread one:
...
RefCounter localRC = globalRC;
...
// thread two:
...
globalRC = NULL;
...
class RefCounter
{
RefCounter(RefCounter& other)
{
// here we go, make a copy
// ooops, thread switch
// other object just got deleted
atomicIncrement(other.count); // access violation
this.count = other.count;
}
}
The only solution I see is to make ctor and opAssign synchronized:
class RefCounter
{
syncronized this(this) // blitting should be syncronized, too, not
only postblitting
{
++*count;
}
syncronized opAssign(RefCounter other)
{
count = other.count;
++*count;
}
}
*Or* just get rid of the global-accessible refcounted object. All the
refcounted objects should be thread-local. In this case you need no
syncronization and no atomicity.
More information about the Digitalmars-d
mailing list