[Nickle]Mutex replacements in nickle
Keith Packard
keithp@keithp.com
Tue, 13 Mar 2001 00:32:22 -0800
Nickle currently has two built-in synchronization types 'mutex' and
'semaphore'. Of course, 'semaphore' is more powerful, but the current
'mutex' type has the nice property that only the thread which acquired the
mutex may release it (else an exception is raised). The code also checks
to see if the current thread holds the mutex and raises the 'deadlock'
exception.
The obvious question is then can mutexes be implemented in terms of
semaphores, and naturally the answer is 'yes'. However, when I sent this
code to Bart, he was confused by the locking order within the objects.
Here's the datatype:
typedef union {
thread;
integer;
} nmt;
public typedef struct {
semaphore sem;
semaphore crit;
nmt t;
} nmutex_struct;
public typedef *nmutex_struct nmutex;
A mutex is a reference to a data structure containing two semaphores and
an object which refers either to the thread owning the mutex or '0' which
indicates that no thread holds the mutex.
The 'crit' semaphore protects access to the 't' variable, the 'sem'
variable is used to block waiting for the mutex to become available.
I wrote the acquire/release functions as:
public integer function acquire (nmutex m)
{
twixt (Semaphore::wait (m->crit); Semaphore::signal (m->crit))
{
if (m->t == Thread::current())
raise deadlock (m);
twixt (Semaphore::signal (m->crit); Semaphore::wait (m->crit))
Semaphore::wait (m->sem);
m->t = Thread::current();
}
return 1;
}
public integer function release (nmutex m)
{
twixt (Semaphore::wait (m->crit); Semaphore::signal (m->crit))
{
if (m->t != Thread::current())
raise notowned (m);
m->t = 0;
Semaphore::signal (m->sem);
}
return 1;
}
The funny part is inside 'acquire' where it waits for the 'crit'
semaphore, checks for deadlock then releases the 'crit' semaphore to wait
for the 'sem' semaphore. The problem is that if the code blocks on the
'sem' semaphore with the 'crit' semaphore locked, there's no way a thread
can ever signal the 'sem' semaphore -- it must wait for the 'crit'
semaphore first. Classic deadlock. So, I signal the 'crit' semaphore
before attempting to wait for the 'sem' semaphore.
I think this could be better written as:
public function acquire (nmutex m)
{
twixt (Semaphore::wait (m->crit); Semaphore::signal (m->crit))
if (m->t == Thread::current())
raise deadlock (m);
Semaphore::wait(m->sem);
twixt (Semaphore::wait (m->crit); Semaphore::signal (m->crit))
m->t = Thread::current ();
}
public integer function release (nmutex m)
{
twixt (Semaphore::wait (m->crit); Semaphore::signal (m->crit))
{
if (m->t != Thread::current())
raise notowned (m);
m->t = 0;
}
Semaphore::signal (m->sem);
return 1;
}
Now the semaphore usage is much clearer -- the 'sem' semaphore is always used
with the 'crit' semaphore unlocked.
-keith