Reentrant mutex

From Wikipedia, the free encyclopedia
Jump to: navigation, search

In computer science, a reentrant or recursive mutex or lock is a mutual exclusion (mutex) device that may be locked multiple times by the same process/thread. While any attempt to perform the "lock" operation on an ordinary mutex (lock) would either fail or block when the mutex is already locked, on a recursive mutex this operation will succeed if and only if the locking thread is the one that already holds the lock. Typically, a recursive mutex tracks the number of times it has been locked, and requires equally many unlock operations to be performed before other threads may lock it.

Motivation[edit]

Recursive mutexes solve the problem of non-reentrancy with regular mutexes: if a function that takes a lock and executes a callback is itself called by the callback, deadlock ensues.[1] In pseudocode, that is the following situation:

var m : Mutex  // A non-recursive mutex, initially unlocked.

function lock_and_call(i : Integer)
    m.lock()
    callback(i)
    m.unlock()

function callback(i : Integer)
    if i > 0
        lock_and_call(i - 1)

Given these definitions, the function call lock_and_call(1) will cause the following sequence of events:

  • m.lock() — mutex locked
  • callback(1)
  • lock_and_call(0) — because i > 0
  • m.lock() — deadlock, because m is already locked, so the executing thread will block, waiting for itself.

Replacing the mutex with a recursive one solves the problem, because the final m.lock() will succeed without blocking.

Practical use[edit]

Stevens notes that recursive locks are "tricky" to use correctly, and recommends their use for adapting single-threaded code without changing APIs, but "only when no other solution is possible".[2]

The Java language's native synchronization mechanism, monitor, uses recursive locks. Syntactically, a lock is a block of code with the 'synchronized' keyword preceding it and any Object reference in parentheses that will be used as the mutex. Inside the synchronized block, the given object can be used as a condition variable by doing a wait(), notify(), or notifyAll() on it. Thus all Objects are both recursive mutexes and condition variables.[3]

Software emulation[edit]

Software emulation can be accomplished using the following structure:[citation needed]

  • A "control" condition using a regular lock
  • Owner identifier, unique to each thread (defaulting to empty / not set)
  • Acquisition count (defaulting to zero)

Acquisition[edit]

  1. Acquire the control condition.
  2. If the owner is set and not the current thread, wait for the control condition to be notified (this also releases the condition).
  3. Set the owner to the current thread. The owner identifier should have already been cleared at this point unless the acquirer is already the owner.
  4. Increment the acquisition count (should always result in 1 for new owners).
  5. Release the control condition.

Release[edit]

  1. Acquire the control condition, asserting that the owner is the releaser.
  2. Decrement the acquisition count, asserting that the count is greater than or equal to zero.
  3. If the acquisition count is zero, clear the owner information and notify the control condition.
  4. Release the control condition.

References[edit]

  1. ^ Buschmann, Frank; Henney, Kevin; Schmidt, Douglas C. (2007). Pattern-Oriented Software Architecture, A Pattern Language for Distributed Computing. John Wiley & Sons. p. 374. 
  2. ^ Stevens, W. Richard; Rago, Stephen A. (2013). Advanced Programming in the UNIX Environment. Addison-Wesley. p. 434. 
  3. ^ David Hovemeyer. "Lecture 17: Java Threads, Synchronization". CS 365 - Parallel and Distributed Computing. Lecture notes, York College of Pennsylvania. Retrieved 2015-06-04.