Scope Locks: The "shared" middle ground
Russell Lewis
webmaster at villagersonline.com
Thu Aug 7 13:47:04 PDT 2008
This post is related to the "Sharing in D" thread. It relates first to
the idea that shared variables will be very limited in what you can do
with them, and second to the thought that there should be some standard
"middle ground" between shared and unshared data, like "const" is to
"invariant" and mutable. I think that the middle ground is "scope",
along with language-supported lockes.
I came up with the idea of Scope Locks a while back based on a comment
that Walter made that "there is no way to enforce that people use locks
properly." The design I'm posting here isn't totally refined yet, but I
thought that we ought to get it into the mix before the design of
"shared" gets too far.
SCOPE LOCKS IN GENERAL
The concept of a Scope Lock is a lock which (transitively) protects a
piece of data. The data is actually contained *within* the lock, and
cannot be accessed (even read) externally.
When you lock a scope lock, it calls a callback that you provide,
passing a pointer to the data as a "scope" argument. (Are scope
arguments implemented yet? I haven't tested it...) Since this is a
scope argument, you cannot save a copy of this pointer (or anything that
it points to) after the function returns.
If you lock a Scope Lock in exclusive mode, then the scope object that
you are passed is mutable, but if you lock it in shared mode, then the
copy will be const.
The lock is automatically released when you return from the function.
APPLICABILITY TO SHARED
I propose that we add a new modified, "locked", along with the "shared"
modifier. "locked" variables are "shared" variables which you can make
unshared by locking them. This could happen using something like the
"with" syntax:
BEGIN CODE
locked MyStruct foo;
void bar()
{
// it is illegal to use any fields of foo out here
unlock-read(foo)
{
// in this block, foo is a non-shared const
writefln("current state = ", foo.field1);
}
unlock-write(foo)
{
// in this block, foo is non-shared and mutable
foo.field1++;
}
}
END CODE
We could then allow "unlock" to be applied to function arguments, as a
way to unify "shared" and unshared code. This would be syntax sugar for
grabbing the lock, and then calling the function:
BEGIN CODE
// note that this argument has to be 'scope' or else the locking
// scheme doesn't work
void baz(scope MyStruct *thing) {...}
void fred()
{
baz(unlock-write(foo));
}
END CODE
OPEN ISSUES & LIMITATIONS
- Scope locks don't solve deadlock issues
- Scope locks should probably allow recursive locks (no self-deadlock)
- "const locked" should probably collapse to "locked" (that is, "const"
is not transitive through "locked") so that, in the case of nested
locks, you can lock a member in exclusive mode even if the container is
shared)
- With nested locks, there's no way to relase the outer lock while you
still hold the inner (without some more language features)
- "scope" is difficult to use in some situations. If you take two
pointers to members of the same scope object, and you pass those
pointers to another function, how do you express to the other that it's
safe to store pointers to one in the other?
Thoughts?
More information about the Digitalmars-d
mailing list