will silently corrupt the state of the shared object completely): one thread may step on
the work done so far by another whose operations are still in progress. The extent to
which this becomes an issue varies per application, and sometimes it isn’t an issue at all.
But even things that aren’t obviously at risk may be at risk. Files and streams, for ex-
ample, are shared by all threads in a program; if multiple threads write to one stream
at the same time, the stream might wind up with interleaved, garbled data. Exam-
ple 5-6 of the prior section was a simple demonstration of this phenomenon in action,
but it’s indicative of the sorts of clashes in time that can occur when our programs go
parallel. Even simple changes can go awry if they might happen concurrently. To be
robust, threaded programs need to control access to shared global items like these so
that only one thread uses them at once.
Luckily, Python’s _thread module comes with its own easy-to-use tools for synchro-
nizing access to objects shared among threads. These tools are based on the concept
of a lock—to change a shared object, threads acquire a lock, make their changes, and
then release the lock for other threads to grab. Python ensures that only one thread can
hold a lock at any point in time; if others request it while it’s held, they are blocked
until the lock becomes available. Lock objects are allocated and processed with simple
and portable calls in the _thread module that are automatically mapped to thread lock-
ing mechanisms on the underlying platform.
For instance, in Example 5-7, a lock object created by _thread.allocate_lock is ac-
quired and released by each thread around the print call that writes to the shared
standard output stream.
Example 5-7. PP4E\System\Threads\thread-count-mutex.py
"""
synchronize access to stdout: because it is shared global,
thread outputs may be intermixed if not synchronized
"""
import _thread as thread, time
def counter(myId, count): # function run in threads
for i in range(count):
time.sleep(1) # simulate real work
mutex.acquire()
print('[%s] => %s' % (myId, i)) # print isn't interrupted now
mutex.release()
mutex = thread.allocate_lock() # make a global lock object
for i in range(5): # spawn 5 threads
thread.start_new_thread(counter, (i, 5)) # each thread loops 5 times
time.sleep(6)
print('Main thread exiting.') # don't exit too early
Really, this script simply augments Example 5-6 to synchronize prints with a thread
lock. The net effect of the additional lock calls in this script is that no two threads will
194 | Chapter 5: Parallel System Tools