The Department of Computer Science & Engineering |
STUART C. SHAPIRO: CSE
305
|
I will discuss concurrency by showing examples that illustrate concurrency as independent threads that must coordinate with each other because they share some common resource. I will use the Python thread module for these examples.
Here's a program that shows threads running independently:
#! /util/bin/python import thread # Program thread.py # Demonstrates that multiple threads can execute in an interleaved fashion. Max = 1000000 def count(label,max): for i in range(max): print label + ":", i print "Starting thread.py" thread.start_new_thread(count, ("B", Max)) thread.start_new_thread(count, ("C", Max)) count("A", Max)
If Max
is too small, thread A
will exit
before the other treads get their chances, and, in our system,
all threads will terminate. An IO interrupt will block a
thread, and give others their chance:
#! /util/bin/python import thread # Program threadIOWait.py # Demonstrates use of IO interrupt to pass control to other threads Max = 100 def count(label,max): for i in range(max): print label + ":", i print label, "is finished." print "Starting threadIOWait.py" thread.start_new_thread(count, ("B", Max)) thread.start_new_thread(count, ("C", Max)) count("A", Max) raw_input("A is finished, OK?")
Threads can share resources, but there can be problems:
#! /util/bin/python import thread # Program threadCount.py # Demonstrates problems when concurrent processes use common resources. n = 0 Max = 10000 def count(label,max): global n for i in range(max): n += 1 print label + ":", i, "count is", n print label, "is finished." print "Starting threadCount.py" thread.start_new_thread(count, ("B", Max)) thread.start_new_thread(count, ("C", Max)) count("A", Max) raw_input("A is finished, OK?") print "Total count is", n
Resources can be shared more equitably by the use of locks, also called semaphores:
#! /util/bin/python import thread # Program threadCountLocks.py # Demonstrates use of a lock (semaphore) to block resource contention. n = 0 Max = 10000 lock = thread.allocate_lock() def count(label,max): global n for i in range(max): lock.acquire() n += 1 lock.release() print label + ":", i, "count is", n print label, "is finished." print "Starting threadCountLocks.py" thread.start_new_thread(count, ("B", Max)) thread.start_new_thread(count, ("C", Max)) count("A", Max) raw_input("A is finished, OK?") print "Total count is", n
We can combine semaphores with a global active thread counter to have the main subroutine wait until all child threads are done, without doing a needless read:
#! /util/bin/python import thread # Program threadCountLocksWait.py # Demonstrates use of a lock (semaphore) to block resource contention, # and a global active thread counter to make sure all threads finish. n = 0 counterLock = thread.allocate_lock() activeThreads = 0 activeThreadsLock = thread.allocate_lock() Max = 100 def incActiveThreads(delta): global activeThreads activeThreadsLock.acquire() activeThreads += delta activeThreadsLock.release() def count(label,max): global n for i in range(max): counterLock.acquire() n += 1 counterLock.release() print label + ":", i, "count is", n print label, "is finished." incActiveThreads(-1) print "Starting threadCountLocksWait.py" incActiveThreads(1) thread.start_new_thread(count, ("B", Max)) incActiveThreads(1) thread.start_new_thread(count, ("C", Max)) incActiveThreads(1) count("A", Max) while activeThreads > 0: pass print "Total count is", n
The major example of this section will be an eager-beaver or
implemented in Python.
First, a short demonstration of the use of closures for passing
expressions:
#! /util/bin/python # Program closures.py # Demonstrates the use of closures def evalit(closures): for f in closures: print f, "=", f() def subA(): def subB(): loc = 3 evalit([lambda:loc, lambda:nloc, lambda:glob]) nloc = 7 subB() glob = 9 subA()
Next, a short-circuit or
implemented in Python:
#! /util/bin/python # Program shortCircuitOr.py # Demonstrates the use of lambda and function application for passing expressions n = 0 def scOr(closures): fcount = 1 for f in closures: print "Evaluating expression", fcount fcount += 1 if f(): return True return False def sub(): m = 7 return scOr([lambda:n>3, lambda:m==7, lambda:False]) n += 1 if sub(): print "It's true" else: print "It's false"
The problem with short-circuit operators is that they evaluate their arguments in order, and it might be that evaluating some early argument is very slow, while evaluating a later argument is very fast:
#! /util/bin/python # Program shortCircuitOrSlow.py # Shows that short circuit or can be slow def scOr(closures): fcount = 1 for f in closures: print "Evaluating expression", fcount fcount += 1 if f(): return True return False def slowFalse(): for i in range(10000000): pass return False def sub(): return scOr([lambda:slowFalse(), lambda:True, lambda:slowFalse]) if sub(): print "It's true" else: print "It's false"
Now for the eager-beaver or
:
#! /util/bin/python import thread # Program eagerBeaverOr.py def ebOr(closures): global activeThreads global orResult orResult = False orResultLock = thread.allocate_lock() global activeThreads activeThreads = 0 activeThreadsLock = thread.allocate_lock() def incActiveThreads(delta): global activeThreads activeThreadsLock.acquire() activeThreads += delta activeThreadsLock.release() def evalDisjunct(d): print "Starting a thread" global orResult if not orResult: myResult = d() orResultLock.acquire() orResult = orResult or myResult orResultLock.release() incActiveThreads(-1) print "Quitting a thread." for f in closures: incActiveThreads(1) thread.start_new_thread(evalDisjunct, (f,)) while (not orResult) and (activeThreads>0): pass return orResult def slowFalse(): for i in range(10000000): pass return False def sub(): return ebOr([lambda:slowFalse(), lambda:True, lambda:slowFalse]) if sub(): print "It's true" else: print "It's false"
We'll compare the running times of the eager beaver or
with the short-circuit or
(using timex
),
both using slowFalse
: