• Open Menu Close Menu
  • Apple
  • Shopping Bag
  • Apple
  • Mac
  • iPad
  • iPhone
  • Watch
  • TV
  • Music
  • Support
  • Search apple.com
  • Shopping Bag

Lists

Open Menu Close Menu
  • Terms and Conditions
  • Lists hosted on this site
  • Email the Postmaster
  • Tips for posting to public mailing lists
RE: Thread locks in 10.3
[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

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

  • Prev by Date: RE: Thread locks in 10.3
  • Next by Date: SocketFilter, Sockets & PCB
  • Previous by thread: Re: Thread locks in 10.3
  • Next by thread: SocketFilter, Sockets & PCB
  • Index(es):
    • Date
    • Thread