The Department of Computer Science & Engineering
cse@buffalo

CSE 305
Programming Languages
Lecture Notes
Stuart C. Shapiro
Spring, 2005


Concurrency

Read Sebesta, Chapter 13.

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:

First Previous Next

Copyright © 2003-2005 by Stuart C. Shapiro. All rights reserved.

Stuart C. Shapiro <shapiro@cse.buffalo.edu>