| .TH LOCK 3 |
| .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 locks, |
| and |
| .B Rendezes |
| are rendezvous points. |
| .PP |
| Locks and rendezvous points have trivial implementations in programs |
| not using the thread library |
| (see |
| .IR thread (3)), |
| since such programs have no concurrency. |
| .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 thread 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 . |
| .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 SOURCE |
| .B \*9/src/lib9/qlock.c |
| .br |
| .B \*9/src/libthread |
| .SH BUGS |
| .B Locks |
| are not always spin locks. |
| Instead they are usually implemented using the |
| .I pthreads |
| library's |
| .BR pthread_mutex_t , |
| whose implementation method is not defined. |
| .PP |
| On |
| .IR pthreads -based |
| systems, the implementation of |
| .B Lock |
| never calls |
| .I pthread_mutex_destroy |
| to free the |
| .BR pthread_mutex_t 's. |
| This leads to resource leaks on FreeBSD 5 |
| (though not on Linux 2.6, where |
| .I pthread_mutex_destroy |
| is a no-op). |
| .BR |
| .PP |
| On systems that do not have a usable |
| .I pthreads |
| implementation, the |
| .B Lock |
| implementation provided by |
| .I libthread |
| is still not exactly a spin lock. |
| 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. |
| Another 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. |