4.2.6. Multi-threaded Programming

Modern programming languages do vary slightly in how threads of a process are created, managed and synchronize access to critical sections. But C/C++, Java and Python all provide the needed facilities. We will mostly look at Python’s facilities, which are quite straight forward. Python provides synchronization facilities very similar to C/C++, except at the object level. Python’s synchronization objects take care of most of the lower level details for us. Yet, it still provides a great deal of flexibility to the programmer.

For more details, see Python’s documentation

4.2.6.1. Creating threads in Python

Threads are created via the threading.Thread class from the threading module. There are three ways that this can be accomplished.

  • Create Thread instance, passing in a function
  • Create Thread instance, passing in a class
  • Subclass Thread and create subclass instance
class threading.Thread(target, args)
Parameters:
  • target (callable function) – identifies the code (function) for the new thread to execute
  • args (list) – is the arguments to pass to the target function
setDaemon(n)

Manage the persistence of the child thread relative to the parent. n of True or 1 means that the child thread dies if the parent dies first. n of False or 0 means that the child thread can keep running after parent is finished.

start()

Begin execution of the thread now.

join()

The current (parent) thread should suspend until the child thread terminates.

In the following simple example, threadcode is a function with arguments of arg1 and arg2:

import threading

# . . .

t = threading.Thread( target = threadcode, args = [arg1, arg2] )
t.setDaemon(1)
t.start()
# . . .
t.join()

4.2.6.2. Synchronization tools (some of them)

4.2.6.2.1. Python’s Simple Lock

class threading.Lock

A simple mutual exclusion lock used to limit access to one thread. This is a semaphore with s = 1.

acquire()

Obtain a lock. The process is blocked until the lock is available.

release()

Release the lock and if another thread is waiting for the lock, wake up that thread.

Here is how to provide mutual exclusion access to a critical section:

import threading

L = threading.Lock()

L.acquire()
# The critical section ...
L.release()

4.2.6.2.2. Python’s Semaphore

class threading.Semaphore(s)

A general purpose lock used to limit access to s threads (Lock and Semaphore are almost the same) – the default value of s is 1. (see Dijkstra’s Semaphore)

acquire()

Obtain a semaphore. The process is blocked until the semaphore is available.

release()

Release the semaphore and if another thread is waiting for the semaphore, wake up that thread.

The following code allows up to five threads in the critical section at one time:

import threading

S = threading.Semaphore(5)

S.acquire()
#
# The critical section ...
#
S.release()

4.2.6.2.3. Python’s Conditional Monitor

class threading.Condition

Implementation of a monitor. (See The Monitor) It allows a thread to wait and be signaled by another thread based on some condition possibly becoming True.

acquire()

Obtain the monitor’s internal lock. The process is blocked until the lock is available.

release()

Release the monitor’s internal lock and if another thread is waiting for the lock, wake up that thread.

wait()

Release the underlying lock, and then block until it is awakened by a notify() or notifyAll() call for the same condition variable in another thread. Once awakened, it re-acquires the lock and returns.

notify()

Wake up one of the threads waiting for the condition variable, if any are waiting.

notifyAll()

Wake up all of the threads waiting for the condition variable, if any are waiting.

The Condition class provides a level of abstraction, which can greatly simplify the solution to many problems. Notice that the conditional wait() statement should always be used inside a while loop. This ensures that the logical condition is still true before the thread enters the critical section. It could be that another thread saw a condition, which prompted it to issue a notify() statement, but by the time our thread returned from wait(), the condition is no longer true. The evaluation of the condition must be done while holding a mutual exclusion lock. It should be pointed out that the Python Condition class contains a mutual exclusion lock, which may be manually acquired or released. The wait() method releases the lock, and then blocks until it is awakened by a notify() or notifyAll() call for the same condition variable in another thread. Once awakened, it re-acquires the lock and returns.

Condition is especially useful for problems such as the producer – consumer (Bounded Buffer (Producers and Consumers)) problem, where each thread may only proceed if certain resources are available. The example below uses a global boolean variable to coordinate access to the critical section, but a boolean function or class method could also be used.

import threading

global available
C = threading.Condition()

C.acquire()
while not available:
    C.wait()
available = False
C.release()

# The critical section. Note that no locks are held.

C.acquire()
available = True
C.notify()
C.release()

# alternately, we could notify all waiting threads
#    C.notifyAll()