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