LOCK(3) LOCK(3)
NAME
lock, canlock, unlock, qlock, canqlock, qunlock, rlock,
canrlock, runlock, 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
Page 1 Plan 9 (printed 10/29/25)
LOCK(3) LOCK(3)
of queueing locks, and Rendezes are rendezvous points.
Locks and rendezvous points have trivial implementations in
programs not using the thread library (see thread(3)), since
such programs have no concurrency.
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 successful, 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 thread until it is released.
Although Locks are the more primitive lock, they have limi-
tations; 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 rwakeupall. 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. Rwakeupall wakes all processes sleeping on r,
returning the number of processes 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.
A Ref contains a long that can be incremented and
Page 2 Plan 9 (printed 10/29/25)
LOCK(3) LOCK(3)
decremented atomically: Incref increments the Ref in one
atomic operation. Decref atomically decrements the Ref and
returns zero if the resulting value is zero, non-zero other-
wise.
SOURCE
/usr/local/plan9/src/lib9/qlock.c
/usr/local/plan9/src/libthread
BUGS
Locks are not always spin locks. Instead they are usually
implemented using the pthreads library's pthread_mutex_t,
whose implementation method is not defined.
On pthreads-based systems, the implementation of Lock never
calls pthread_mutex_destroy to free the pthread_mutex_t's.
This leads to resource leaks on FreeBSD 5 (though not on
Linux 2.6, where pthread_mutex_destroy is a no-op).
On systems that do not have a usable pthreads implementa-
tion, the Lock implementation provided by libthread is still
not exactly a spin lock. After each unsuccessful attempt,
lock calls sleep(0) to yield the CPU; this handles the com-
mon case where some other process holds the lock. After a
thousand unsuccessful attempts, lock sleeps for 100ms
between attempts. Another another thousand 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 dis-
cussed 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.
Page 3 Plan 9 (printed 10/29/25)