| Thread overview | ||||||
|---|---|---|---|---|---|---|
|
January 09, 2008 How to use synchronized() {} as the basis for a freely (un)lockable mutex, using stackthreads | ||||
|---|---|---|---|---|
| ||||
Disclaimer: My StackThreads are neither particularly fast (130 cycles per context switch), nor particularly stable. This is primarily intended as a Proof of Concept, even though I do use it in some of my code. :)
Have you ever wished D had a Mutex class that could be locked or unlocked at any time?
D's synchronized() {} statement is nice and all, but does have some weaknesses, primarily that it can only be used to
synchronize some scope - it is not possible to unlock the underlying mutex in the middle of a block.
Of course, I could write code that depends upon the OS' mutexes, but that's hardly in the spirit of D.
Of course, I could write a busy-spin based mutex class, but it is my firm belief that busyspin *expletive* *expletive*.
So, there remains only one solution: twist synchronized(){} so that it becomes freely (un)lockable.
This requires a way to break out of the middle of a synchronized block, without ending the synchronization.
Stackthreads offer such a way.
Consider the following code from scrapple.tools.threads:
>
> import tools.stackthreads, std.thread;
> class Lock {
> StackThread!(bool, void)[] toggle;
> Thread[] toggle_thr;
> Object toggle_sync;
>
> this() { toggle_sync=new Object; }
>
> StackThread!(bool, void) getMyST() {
> auto thr=Thread.getThis();
> foreach (id, entry; toggle_thr) if (entry is thr) return toggle[id];
> auto nt=stackthread=(Object This, bool delegate() which) {
> while (true) {
> if (!which()) throw new Exception("Cannot double-free lock");
> synchronized(This) if (which()) throw new Exception("Cannot double-claim lock");
> }
> } /fix/ this;
> synchronized (toggle_sync) { toggle_thr ~= thr; toggle ~= nt; }
> return nt;
> }
>
> void lock(bool locking) { getMyST()(locking); }
> void lock() { lock(true); }
> void unlock() { lock(false); }
> void Synchronized(void delegate() dg) { lock; scope(exit) unlock; dg(); }
> void Unsynchronized(void delegate() dg) { unlock; scope(exit) lock; dg(); }
> }
What does this code do?
Basically, it assigns every thread that tries to use the lock, a Stackthread (slightly wasteful but meh). getMyST returns the current thread's Stackthread, or creates it if it doesn't exist yet.
Basically, each StackThread takes bools for input, and nothing for output ( "!(bool, void)" ).
The ST must be fed true and false in turn; true for "Go and enter the synchronized {} block", and false for "Leave the synchronized {} block".
(
For those unfamiliar with stackthreads, whenever the delegate calls "which", the stackthread is suspended until the surrounding
function calls it with a bool value. At this point, the stackthread delegate resumes, with "which" returning the value the stackthread
was called with.
)
An example application would be the creation of a cache class.
class Cache(VAL, KEY) {
VAL[KEY] buffer;
Lock lock;
VAL delegate(KEY) dg;
this(typeof(dg) _dg) { dg=_dg; New(lock); }
VAL get(KEY key) {
VAL res;
lock.Synchronized = {
if (key in buffer) res = buffer[key];
else {
lock.Unsynchronized = { res = dg(key); };
buffer[key] = res;
};
return res;
}
}
And yes, I know this class doesn't handle the case of simulataneous evaluation of dg with the same key. It's only an example. :)
--downs
| ||||
January 09, 2008 Re: How to use synchronized() {} as the basis for a freely (un)lockable mutex, using stackthreads | ||||
|---|---|---|---|---|
| ||||
Posted in reply to downs | downs wrote: > Disclaimer: My StackThreads are neither particularly fast (130 cycles per context switch), nor particularly stable. > This is primarily intended as a Proof of Concept, even though I do use it in some of my code. :) > > Have you ever wished D had a Mutex class that could be locked or unlocked at any time? Tango does :-) > D's synchronized() {} statement is nice and all, but does have some weaknesses, primarily that it can only be used to > synchronize some scope - it is not possible to unlock the underlying mutex in the middle of a block. The Tango mutexes can also be used with the 'synchronized' statement. However, I'm not sure I like the idea of being inside a 'synchronized' block and having the mutex unlocked. Why not just break the code into two sequential 'synchronized' blocks? Sean | |||
January 09, 2008 Re: How to use synchronized() {} as the basis for a freely (un)lockable mutex, using stackthreads | ||||
|---|---|---|---|---|
| ||||
Posted in reply to Sean Kelly | Sean Kelly wrote: > downs wrote: >> Disclaimer: My StackThreads are neither particularly fast (130 cycles >> per context switch), nor particularly stable. >> This is primarily intended as a Proof of Concept, even though I do use >> it in some of my code. :) >> >> Have you ever wished D had a Mutex class that could be locked or unlocked at any time? > > Tango does :-) > Clarification. D/Phobos. >> D's synchronized() {} statement is nice and all, but does have some >> weaknesses, primarily that it can only be used to >> synchronize some scope - it is not possible to unlock the underlying >> mutex in the middle of a block. > > The Tango mutexes can also be used with the 'synchronized' statement. However, I'm not sure I like the idea of being inside a 'synchronized' block and having the mutex unlocked. Why not just break the code into two sequential 'synchronized' blocks? > > > Sean Because the structure of the code looks like this: foo; synchronized { bar; whee { lol; *unsynchronized* { lmao; } meep; } baz; } So, to break it into two synchronized statements, my code would have to look like so ... foo; synchronized { bar; whee { lol; } lmao; synchronized { meep; } // closing bracket of whee baz; } See the problem? :) --downs | |||
January 09, 2008 Re: How to use synchronized() {} as the basis for a freely (un)lockable mutex, using stackthreads | ||||
|---|---|---|---|---|
| ||||
Posted in reply to downs | downs wrote:
> Sean Kelly wrote:
>> downs wrote:
>>> Disclaimer: My StackThreads are neither particularly fast (130 cycles
>>> per context switch), nor particularly stable.
>>> This is primarily intended as a Proof of Concept, even though I do use
>>> it in some of my code. :)
>>>
>>> Have you ever wished D had a Mutex class that could be locked or
>>> unlocked at any time?
>> Tango does :-)
>>
>
> Clarification. D/Phobos.
>
>>> D's synchronized() {} statement is nice and all, but does have some
>>> weaknesses, primarily that it can only be used to
>>> synchronize some scope - it is not possible to unlock the underlying
>>> mutex in the middle of a block.
>> The Tango mutexes can also be used with the 'synchronized' statement.
>> However, I'm not sure I like the idea of being inside a 'synchronized'
>> block and having the mutex unlocked. Why not just break the code into
>> two sequential 'synchronized' blocks?
>>
>>
>> Sean
>
> Because the structure of the code looks like this:
>
> foo;
> synchronized {
> bar;
> whee {
> lol;
> *unsynchronized* { lmao; }
> meep;
> }
> baz;
> }
>
> So, to break it into two synchronized statements, my code would have to look like so ...
>
> foo;
> synchronized {
> bar;
> whee {
> lol;
> }
> lmao;
> synchronized {
> meep;
> } // closing bracket of whee
> baz;
> }
>
> See the problem? :)
I can't say I've ever actually seen the need for such an algorithm in practice (outside of condition variables), but in Tango you could do:
synchronized( mutex )
{
bar;
whee
{
lol;
{
mutex.unlock;
scope(exit) mutex.lock;
meep;
}
baz;
}
}
Can't say I have a solution for Phobos though.
Sean
| |||
Copyright © 1999-2021 by the D Language Foundation
Permalink
Reply