RE: Thread locks in 10.3
RE: Thread locks in 10.3
- Subject: RE: Thread locks in 10.3
- From: Quinn <email@hidden>
- Date: Thu, 19 Oct 2006 11:41:53 +0100
Title: RE: Thread locks in 10.3
At 13:53 -0400 18/10/06, Carl Smith wrote:
I
guess in retrospect I could some direction where to read up on the
10.4 locking design. If anyone knows where I could find this document
and or some examples I would appreciate it.
Here's my conditional variable primer...
---------------------------------------------------------------------------
[...] you'll need to learn more about the standard UNIX
threading model. I'm going to explain this in terms of pthread
condition variables, because:
1. the msleep/wakeup threading model used by the Mac OS X kernel is
more-or-less equivalent to pthread condition variables
2. pthread condition variables are well documented by third party
sources
Once you understand one (pthread condition variables), it's easy to
understand the other (msleep/wakeup).
pthreads offers two key synchronisation mechanisms:
o mutexes
o condition variables
Mutexes are solely used to maintain the coherency of shared data.
For example, consider the classic "incrementing a global
problem". If you run the following code without a mutex
from more than one thread concurrently, you run the risk of missing an
increment.
static int gCounter;
gCounter += 1
This is because the increment breaks down to:
tmp =
gCounter; <<< line A
tmp += 1;
gCounter =
1; <<< line
B
and if another thread runs the same code after the completion of line
A but before the completion of line B, things go wrong.
To solve this, you bracket the increment with a mutex:
(void) pthread_mutex_lock(&gMutex);
gCounter += 1;
(void) pthread_mutex_unlock(&gMutex);
Only one thread of execution can acquire the mutex at any given point
in time, so gCounter is updated consistently.
Note:
In this simple example, you can do the equivalent
with atomic
operations. However, that doesn't work in all
cases. For
example, it's very hard to update a doubly linked
list using
atomic operations.
There are two things to remember about mutexes:
1. They should be a held for a short time. Ideally, you should
only hold a mutex while you update some shared state.
2. They must be locked and unlocked by the same thread.
So, a mutex is great for protecting shared state (like incrementing
and decrementing a global variable consistently), but it doesn't work
if you want to block waiting for some condition to be met (like, for
example, waiting for that global variable to hit 10). For that
you use a condition variable.
A condition variable is something that you can block on while waiting
for a condition to be satisfied. It doesn't store a value
itself; it's merely a place used to record that someone is waiting.
The actual condition associated with the condition variable is stored
in some other variable, or set of variables. These, in turn,
must be protected from multi-threaded access by a mutex. So, you
always use a condition variable in concert with a mutex.
For example, let's say that you want to wait until the value of
gCounter hits 10. The value in gCounter is protected by a mutex,
just as it was in my previous example. However, now you have a
condition variable that's used to block waiting for that condition.
Here's a trivial example of a waiter:
pthread_cond_t gCondVar;
(void) pthread_mutex_lock(&gMutex);
while (gCounter != 10) {
(void) pthread_cond_wait(&gCondVar,
&gMutex);
}
// at this point you know that gCounter is 10, so you
// can do something based on that
(void) pthread_mutex_unlock(&gMutex);
and here's the corresponding increment routine:
(void) pthread_mutex_lock(&gMutex);
gCounter += 1;
(void) pthread_cond_broadcast(&gCondVar);
(void) pthread_mutex_unlock(&gMutex);
There are a bunch of things to note about this:
1. The condition variable doesn't store any information about the
state of the condition. Rather, the state is still held in the
gCounter global.
2. That global is still protected by the gMutex.
3. The thread that increments gCounter "signals" the
condition; this wakes up anyone waiting on gCondVar.
4. The thread that's waiting on the condition first acquires gMutex.
Because this mutex protects the state of the condition (gCounter), you
have to acquire it before trying to evaluate that state.
5. It then uses a while loop to wait until the state is correct.
If it's not correct, it blocks on the condition variable. This
blocking routine, pthread_cond_wait, atomically drops the mutex and
blocks on the condition variable. It also reacquires the mutex
before returning.
6. The waiter uses a while loop because the increment routine always
wakes it up, regardless of the value of gCounter. You could
imagine a new version of the increment routine:
(void) pthread_mutex_lock(&gMutex);
gCounter += 1;
if (gCounter == 10) {
(void)
pthread_cond_broadcast(&gCondVar);
}
(void) pthread_mutex_unlock(&gMutex);
and you might think that this obviates the need for a while
loop. In general, that's not true. The waiter can be woken
up even if the condition isn't true, and it must always check that the
condition is actually true before proceeding.
This code is quite subtle; chances are that you'll have to stare at it
a while before you can understand all of its implications. As I
mentioned earlier, pthreads is well documented by third party
sources. I recommend "Programming with POSIX Threads"
by Butenhof.
<http://www.amazon.com/gp/product/0201633922>
Once you understand this basic model, it's easy to map it to the
kernel. msleep is like pthread_cond_wait; it will atomically
drop a mutex and wait on a 'channel' (in the kernel, any pointer can
be considered a wait channel, which is roughly equivalent to a
condition variable). wakeup is equivalent to
pthread_cond_broadcast. And you use the standard kernel mutex
routines from <kern/locks.h> (lck_mtx_lock, lck_mtx_unlock, and
so on).
---------------------------------------------------------------------------
Share and Enjoy
--
Quinn "The
Eskimo!"
<http://www.apple.com/developer/>
Apple Developer Relations, Developer Technical Support, Core
OS/Hardware
_______________________________________________
Do not post admin requests to the list. They will be ignored.
Darwin-kernel mailing list (email@hidden)
Help/Unsubscribe/Update your Subscription:
This email sent to email@hidden