www.digitalmars.com         C & C++   DMDScript  

digitalmars.D - How to use synchronized() {} as the basis for a freely (un)lockable

reply downs <default_357-line yahoo.de> writes:
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
Jan 09 2008
parent reply Sean Kelly <sean f4.ca> writes:
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
Jan 09 2008
parent reply downs <default_357-line yahoo.de> writes:
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
Jan 09 2008
parent Sean Kelly <sean f4.ca> writes:
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?


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.

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
Jan 09 2008