...
/Using Queues: Sleeping Instead of Spinning
Using Queues: Sleeping Instead of Spinning
This lesson discusses the use of queues to built more efficient locks.
We'll cover the following...
The real problem with the previous approaches is that they leave too much to chance. The scheduler determines which thread runs next; if the scheduler makes a bad choice, a thread runs that must either spin waiting for the lock (our first approach) or yield the CPU immediately (our second approach). Either way, there is potential for waste and no prevention of starvation.
Thus, we must explicitly exert some control over which thread next gets to acquire the lock after the current holder releases it. To do this, we will need a little more OS support, as well as a queue to keep track of which threads are waiting to acquire the lock.
park()
and unpark()
For simplicity, we will use the support provided by Solaris, in terms of two calls: park()
to put a calling thread to sleep and unpark(threadID)
to wake a particular thread as designated by threadID
. These two routines can be used in tandem to build a lock that puts a caller to sleep if it tries to acquire a held lock and wakes it when the lock is free. Let’s look at the code excerpt below to understand one possible use of such primitives.
typedef struct __lock_t {int flag;int guard;queue_t *q;} lock_t;void lock_init(lock_t *m) {m->flag = 0;m->guard = 0;queue_init(m->q);}void lock(lock_t *m) {while (TestAndSet(&m->guard, 1) == 1); //acquire guard lock by spinningif (m->flag == 0) {m->flag = 1; //lock is acquiredm->guard = 0;} else {queue_add(m->q, gettid());m->guard = 0;park();}}void unlock(lock_t *m) {while (TestAndSet(&m->guard, 1) == 1); //acquire guard lock by spinningif (queue_empty(m->q))m->flag = 0; // let go of lock; no one wants itelseunpark(queue_remove(m->q)); // hold lock// (for next thread!)m->guard = 0;}
We do a couple of interesting things in this example. First, we combine the old test-and-set idea with an explicit queue of lock waiters to make a more efficient lock. Second, we use a queue to help control who gets the lock next and thus avoid starvation.
You might notice how the guard is used in this example, basically, as a spin-lock around the flag and queue manipulations, the lock is using. ...