pass-by-ref semantics for structs (was Deque impl.)
Rainer Schuetze
r.sagitario at gmx.de
Fri Feb 1 11:03:51 PST 2013
On 01.02.2013 00:14, Steven Schveighoffer wrote:
> On Thu, 31 Jan 2013 17:37:39 -0500, Steven Schveighoffer
> <schveiguy at yahoo.com> wrote:
>
>> On Thu, 31 Jan 2013 17:31:38 -0500, Rainer Schuetze
>> <r.sagitario at gmx.de> wrote:
>>
>>> Any reference that is accessible from multiple threads, e.g. a global
>>> shared variable.
>>>
>>> shared(Container) allObjects;
>>>
>>> Without any thread having a reference to it, the payload's reference
>>> count is 1. The reading thread above increments the counter when
>>> making a local copy, but the writing thread decrements the counter
>>> when assigning Container.init. If both run concurrently, with the
>>> sequence 1 to 3 above, trouble is inevitable.
>>
>> Right, but isn't that an issue with having ANY shared variable that
>> isn't protected by a lock?
>>
>> I was thinking you had passed the pointer in via a message or something.
>
> BTW, couldn't we solve this with a function that increments the retain
> count, and then returns a pointer to the object? Such a method would
> have to be atomic on shared instances.
The problem is to make it atomic without expensive locks. Lacking the
CAS2 operation (that does a CAS on two arbitrary memory locations
simultaneously), my first thought was that it is not possible.
Considering it again, I guess it can be done by not using atomic
increment/decrement, but splitting up the operations. The main idea is
to no longer touch the data after the reference count has become 0 once:
// CAS returns *var before operation, only modifies if *var == expected
size_t CAS(size_t* var, size_t expected, size_t newval);
struct PayLoad(T)
{
T* array;
size_t size;
size_t refCount;
PayLoad!T* ref()
{
size_t cnt = refCount;
while(cnt)
{
int old = CAS(&refCount, cnt, cnt+1);
if(old == cnt)
return this;
cnt = refCount;
}
// in the rare case the payload has become invalid,
// return a new empty object to avoid having to
// check for null (could also be null if the container
// always checks its payload pointer)
return new PayLoad!T;
}
void deref()
{
size_t cnt, old;
do
{
assert(refCount);
cnt = refCount;
old = CAS(&refCount, cnt, cnt-1);
}
while(old != cnt);
if(refCount == 0)
free(array);
}
}
struct Container(T)
{
PayLoad!T* payload;
this() // I know this doesn't work, but would be convenient
{
payload = new Payload!T;
}
this(this)
{
payload = payload.ref();
}
~this()
{
payload.deref();
}
void opAssign(typeof(this) rhs)
{
if(rhs.payload !is payload)
{
payload.deref();
payload = rhs.payload.ref();
}
}
}
PayLoad!T will still have to be garbage collected, though.
BTW: Maybe I am misunderstanding it, but according to
http://dlang.org/operatoroverloading.html overloading opAssign on the
same type as in "void opAssign(typeof(this) rhs)" is not allowed. Is
this correct? You find it in phobos as well.
Another BTW: the links to anchors on the same page are broken as they
don't include the html file.
More information about the Digitalmars-d
mailing list