313 lines
5.8 KiB
Text
313 lines
5.8 KiB
Text
.TH LOCK 2
|
|
.SH 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
|
|
.SH SYNOPSIS
|
|
.ft L
|
|
.nf
|
|
#include <u.h>
|
|
#include <libc.h>
|
|
.PP
|
|
.ft L
|
|
.nf
|
|
void lock(Lock *l)
|
|
int canlock(Lock *l)
|
|
void unlock(Lock *l)
|
|
.PP
|
|
.ft L
|
|
.nf
|
|
void qlock(QLock *l)
|
|
int canqlock(QLock *l)
|
|
void qunlock(QLock *l)
|
|
.PP
|
|
.ft L
|
|
.nf
|
|
void rlock(RWLock *l)
|
|
int canrlock(RWLock *l)
|
|
void runlock(RWLock *l)
|
|
.PP
|
|
.ft L
|
|
.nf
|
|
void wlock(RWLock *l)
|
|
int canwlock(RWLock *l)
|
|
void wunlock(RWLock *l)
|
|
.PP
|
|
.ft L
|
|
.nf
|
|
typedef struct Rendez {
|
|
QLock *l;
|
|
\fI...\fP
|
|
} Rendez;
|
|
.PP
|
|
.ft L
|
|
.nf
|
|
void rsleep(Rendez *r)
|
|
int rwakeup(Rendez *r)
|
|
int rwakeupall(Rendez *r)
|
|
.PP
|
|
.ft L
|
|
#include <thread.h>
|
|
.PP
|
|
.ft L
|
|
.nf
|
|
typedef struct Ref {
|
|
long ref;
|
|
} Ref;
|
|
.PP
|
|
.ft L
|
|
.nf
|
|
void incref(Ref*)
|
|
long decref(Ref*)
|
|
.fi
|
|
.SH DESCRIPTION
|
|
These routines are used to synchronize processes sharing memory.
|
|
.PP
|
|
.B Locks
|
|
are spin locks,
|
|
.B QLocks
|
|
and
|
|
.B RWLocks
|
|
are different types of queueing rendezvous locks,
|
|
and
|
|
.B Rendezes
|
|
are rendezvous points.
|
|
.PP
|
|
Locks and rendezvous points work in regular programs as
|
|
well as programs that use the thread library
|
|
(see
|
|
.IR thread (2)).
|
|
The thread library replaces the
|
|
.IR rendezvous (2)
|
|
system call
|
|
with its own implementation,
|
|
.IR threadrendezvous ,
|
|
so that threads as well as processes may be synchronized by locking calls
|
|
in threaded programs.
|
|
.PP
|
|
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.
|
|
.PP
|
|
.I Lock
|
|
blocks until the lock has been obtained.
|
|
.I Canlock
|
|
is non-blocking.
|
|
It tries to obtain a lock and returns a non-zero value if it
|
|
was successful, 0 otherwise.
|
|
.I Unlock
|
|
releases a lock.
|
|
.PP
|
|
.B QLocks
|
|
have the same interface but are not spin locks; instead if the lock is taken
|
|
.I qlock
|
|
will suspend execution of the calling task until it is released.
|
|
.PP
|
|
Although
|
|
.B Locks
|
|
are the more primitive lock, they have limitations; for example,
|
|
they cannot synchronize between tasks in the same
|
|
.IR proc .
|
|
Use
|
|
.B QLocks
|
|
instead.
|
|
.PP
|
|
.B RWLocks
|
|
manage access to a data structure that has distinct readers and writers.
|
|
.I Rlock
|
|
grants read access;
|
|
.I runlock
|
|
releases it.
|
|
.I Wlock
|
|
grants write access;
|
|
.I wunlock
|
|
releases it.
|
|
.I Canrlock
|
|
and
|
|
.I 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.
|
|
.PP
|
|
All types of lock should be initialized to all zeros before use; this
|
|
puts them in the unlocked state.
|
|
.PP
|
|
.B Rendezes
|
|
are rendezvous points. Each
|
|
.B Rendez
|
|
.I r
|
|
is protected by a
|
|
.B QLock
|
|
.IB r -> l \fR,
|
|
which must be held by the callers of
|
|
.IR rsleep ,
|
|
.IR rwakeup ,
|
|
and
|
|
.IR rwakeupall .
|
|
.I Rsleep
|
|
atomically releases
|
|
.IB r -> l
|
|
and suspends execution of the calling task.
|
|
After resuming execution,
|
|
.I rsleep
|
|
will reacquire
|
|
.IB r -> l
|
|
before returning.
|
|
If any processes are sleeping on
|
|
.IR r ,
|
|
.I rwakeup
|
|
wakes one of them.
|
|
it returns 1 if a process was awakened, 0 if not.
|
|
.I Rwakeupall
|
|
wakes all processes sleeping on
|
|
.IR r ,
|
|
returning the number of processes awakened.
|
|
.I Rwakeup
|
|
and
|
|
.I rwakeupall
|
|
do not release
|
|
.IB r -> l
|
|
and do not suspend execution of the current task.
|
|
.PP
|
|
Before use,
|
|
.B Rendezes
|
|
should be initialized to all zeros except for
|
|
.IB r -> l
|
|
pointer, which should point at the
|
|
.B QLock
|
|
that will guard
|
|
.IR r .
|
|
It is important that this
|
|
.B QLock
|
|
is the same one that protects the rendezvous condition; see the example.
|
|
.PP
|
|
A
|
|
.B Ref
|
|
contains a
|
|
.B long
|
|
that can be incremented and decremented atomically:
|
|
.I Incref
|
|
increments the
|
|
.I Ref
|
|
in one atomic operation.
|
|
.I Decref
|
|
atomically decrements the
|
|
.B Ref
|
|
and returns zero if the resulting value is zero, non-zero otherwise.
|
|
.SH EXAMPLE
|
|
Implement a buffered single-element channel using
|
|
.I rsleep
|
|
and
|
|
.IR rwakeup :
|
|
.IP
|
|
.EX
|
|
.ta +4n +4n +4n
|
|
typedef struct Chan
|
|
{
|
|
QLock l;
|
|
Rendez full, empty;
|
|
int val, haveval;
|
|
} Chan;
|
|
.EE
|
|
.IP
|
|
.EX
|
|
.ta +4n +4n +4n
|
|
Chan*
|
|
mkchan(void)
|
|
{
|
|
Chan *c;
|
|
|
|
c = mallocz(sizeof *c, 1);
|
|
c->full.l = &c->l;
|
|
c->empty.l = &c->l;
|
|
return c;
|
|
}
|
|
.EE
|
|
.IP
|
|
.EX
|
|
.ta +4n +4n +4n
|
|
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);
|
|
}
|
|
.EE
|
|
.IP
|
|
.EX
|
|
.ta +4n +4n +4n
|
|
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;
|
|
}
|
|
.EE
|
|
.LP
|
|
Note that the
|
|
.B QLock
|
|
protecting the
|
|
.B Chan
|
|
is the same
|
|
.B QLock
|
|
used for the
|
|
.BR Rendez ;
|
|
this ensures that wakeups are not missed.
|
|
.SH SOURCE
|
|
.B /sys/src/libc/port/lock.c
|
|
.br
|
|
.B /sys/src/libc/9sys/qlock.c
|
|
.br
|
|
.B /sys/src/libthread/ref.c
|
|
.SH SEE ALSO
|
|
.I rfork
|
|
in
|
|
.IR fork (2)
|
|
.SH BUGS
|
|
.B Locks
|
|
are not strictly spin locks.
|
|
After each unsuccessful attempt,
|
|
.I lock
|
|
calls
|
|
.B sleep(0)
|
|
to yield the CPU; this handles the common case
|
|
where some other process holds the lock.
|
|
After a thousand unsuccessful attempts,
|
|
.I lock
|
|
sleeps for 100ms between attempts.
|
|
After another thousand unsuccessful attempts,
|
|
.I lock
|
|
sleeps for a full second between attempts.
|
|
.B 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.
|
|
.PP
|
|
It is an error for a program to
|
|
.I 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.
|