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: