LOCK(2)LOCK(2)NAME
lock, canlock, unlock, qlock, canqlock, qunlock, rlock, canrlock, run‐
lock, wlock, canwlock, wunlock, rsleep, rwakeup, rwakeupall, incref,
decref - spin locks, queueing rendezvous locks, reader-writer locks,
rendezvous points, and reference counts
SYNOPSIS
#include <u.h>
#include <libc.h>
void lock(Lock *l)
int canlock(Lock *l)
void unlock(Lock *l)
void qlock(QLock *l)
int canqlock(QLock *l)
void qunlock(QLock *l)
void rlock(RWLock *l)
int canrlock(RWLock *l)
void runlock(RWLock *l)
void wlock(RWLock *l)
int canwlock(RWLock *l)
void wunlock(RWLock *l)
typedef struct Rendez {
QLock *l;
...
} Rendez;
void rsleep(Rendez *r)
int rwakeup(Rendez *r)
int rwakeupall(Rendez *r)
#include <thread.h>
typedef struct Ref {
long ref;
} Ref;
void incref(Ref*)
long decref(Ref*)
DESCRIPTION
These routines are used to synchronize processes sharing memory.
Locks are spin locks, QLocks and RWLocks are different types of queue‐
ing rendezvous locks, and Rendezes are rendezvous points.
Locks and rendezvous points work in regular programs as well as pro‐
grams that use the thread library (see thread(2)). The thread library
replaces the rendezvous(2) system call with its own implementation,
threadrendezvous, so that threads as well as processes may be synchro‐
nized by locking calls in threaded programs.
Used carelessly, spin locks can be expensive and can easily generate
deadlocks. Their use is discouraged, especially in programs that use
the thread library because they prevent context switches between
threads.
Lock blocks until the lock has been obtained. Canlock is non-blocking.
It tries to obtain a lock and returns a non-zero value if it was suc‐
cessful, 0 otherwise. Unlock releases a lock.
QLocks have the same interface but are not spin locks; instead if the
lock is taken qlock will suspend execution of the calling task until it
is released.
Although Locks are the more primitive lock, they have limitations; for
example, they cannot synchronize between tasks in the same proc. Use
QLocks instead.
RWLocks manage access to a data structure that has distinct readers and
writers. Rlock grants read access; runlock releases it. Wlock grants
write access; wunlock releases it. Canrlock and canwlock are the non-
blocking versions. There may be any number of simultaneous readers,
but only one writer. Moreover, if write access is granted no one may
have read access until write access is released.
All types of lock should be initialized to all zeros before use; this
puts them in the unlocked state.
Rendezes are rendezvous points. Each Rendez r is protected by a QLock
r->l, which must be held by the callers of rsleep, rwakeup, and rwakeu‐
pall. Rsleep atomically releases r->l and suspends execution of the
calling task. After resuming execution, rsleep will reacquire r->l
before returning. If any processes are sleeping on r, rwakeup wakes
one of them. it returns 1 if a process was awakened, 0 if not. Rwake‐
upall wakes all processes sleeping on r, returning the number of pro‐
cesses awakened. Rwakeup and rwakeupall do not release r->l and do not
suspend execution of the current task.
Before use, Rendezes should be initialized to all zeros except for r->l
pointer, which should point at the QLock that will guard r. It is
important that this QLock is the same one that protects the rendezvous
condition; see the example.
A Ref contains a long that can be incremented and decremented atomi‐
cally: Incref increments the Ref in one atomic operation. Decref atom‐
ically decrements the Ref and returns zero if the resulting value is
zero, non-zero otherwise.
EXAMPLE
Implement a buffered single-element channel using rsleep and rwakeup:
typedef struct Chan
{
QLock l;
Rendez full, empty;
int val, haveval;
} Chan;
Chan*
mkchan(void)
{
Chan *c;
c = mallocz(sizeof *c, 1);
c->full.l = &c->l;
c->empty.l = &c->l;
return c;
}
void
send(Chan *c, int val)
{
qlock(&c->l);
while(c->haveval)
rsleep(&c->full);
c->haveval = 1;
c->val = val;
rwakeup(&c->empty); /* no longer empty */
qunlock(&c->l);
}
int
recv(Chan *c)
{
int v;
qlock(&c->l);
while(!c->haveval)
rsleep(&c->empty);
c->haveval = 0;
v = c->val;
rwakeup(&c->full); /* no longer full */
qunlock(&c->l);
return v;
}
Note that the QLock protecting the Chan is the same QLock used for the
Rendez; this ensures that wakeups are not missed.
SOURCE
/sys/src/libc/port/lock.c
/sys/src/libc/9sys/qlock.c
/sys/src/libthread/ref.c
SEE ALSO
rfork in fork(2)BUGS
Locks are not strictly spin locks. After each unsuccessful attempt,
lock calls sleep(0) to yield the CPU; this handles the common case
where some other process holds the lock. After a thousand unsuccessful
attempts, lock sleeps for 100ms between attempts. After another thou‐
sand unsuccessful attempts, lock sleeps for a full second between
attempts. Locks are not intended to be held for long periods of time.
The 100ms and full second sleeps are only heuristics to avoid tying up
the CPU when a process deadlocks. As discussed above, if a lock is to
be held for much more than a few instructions, the queueing lock types
should be almost always be used.
It is an error for a program to fork when it holds a lock in shared
memory, since this will result in two processes holding the same lock
at the same time, which should not happen.
LOCK(2)