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
andSemaphore
are almost the same) – the default value of s is 1. (see Dijkstra’s Semaphore)-
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()
ornotifyAll()
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()