The Department of Computer Science & Engineering
cse@buffalo

CSE 305
Programming Languages
Lecture Notes
Stuart C. Shapiro


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.

#! /util/bin/python
import thread

# Program threadIO.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 threadIO.py"
thread.start_new_thread(count, ("B", Max))
thread.start_new_thread(count, ("C", Max))
count("A", Max)

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.

counter = 0
Max = 10000

def incCounter(label,delta):
    global counter
    counter += 1
    print label, "increments counter to: ", counter

def count(label,max):
    global counter
    for i in range(max):
        incCounter(label,1)
    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", counter

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.

counter = 0
Max = 10000
lock = thread.allocate_lock()

def incCounter(label,delta):
    global counter
    lock.acquire()
    counter += delta
    lock.release()
    print label, "increments counter to: ", counter

def count(label,max):
    for i in range(max):
        incCounter(label,1)
    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", counter

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 "private" resource,
#    a lock (semaphore) to block resource contention,
#    and a global active thread counter to make sure all threads finish.

counter = 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 incCounter(label,delta):
    global counter
    counterLock.acquire()
    counter += delta
    print label, "increments counter to: ", counter
    counterLock.release()

def count(label,max):
    for i in range(max):
        incCounter(label,1)
    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", counter

The major example of this section will be an eager-beaver or implemented in Python.
First, a short reminder 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()
---------------------------------------------------------------
<timberlake:ConcurrentPrograms:1:264> python closures.py
<function <lambda> at 0x2ac0bd6f0320> = 3
<function <lambda> at 0x2ac0bd6f0398> = 7
<function <lambda> at 0x2ac0bd6f0410> = 9

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"
--------------------------------------------------------
<timberlake:ConcurrentPrograms:1:265> python shortCircuitOr.py
Evaluating expression 1
Evaluating expression 2
It's true

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
    activeThreads = 0
    activeThreadsLock = thread.allocate_lock()

    def incActiveThreads(delta):
        global activeThreads
        activeThreadsLock.acquire()
        activeThreads += delta
        activeThreadsLock.release()

    global orResult
    orResult = False
    orResultLock = thread.allocate_lock()

    def disjoin(bool):
        global orResult
        orResultLock.acquire()
        orResult = orResult or bool
        orResultLock.release()

    def evalDisjunct(d):
        print "Starting a thread"
        global orResult
        if not orResult:
            disjoin(d())
        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 the Unix time command), both using slowFalse:

<timberlake:ConcurrentPrograms:1:382> time python shortCircuitOrSlow.py
Evaluating expression 1
Evaluating expression 2
It's true
1.393u 0.362s 0:01.77 98.8%     0+0k 0+0io 0pf+0w

<timberlake:ConcurrentPrograms:1:383> time python eagerBeaverOr.py
Starting a threadStarting a thread

 Starting a thread
Quitting a thread.
Quitting a thread.
Quitting a thread.
 It's true
0.012u 0.015s 0:00.03 66.6%     0+0k 0+0io 0pf+0w
"This can be interpreted the following way....
1.189u - the time your command spent processing in user mode
0.334s - the time your command spent processing in kernel mode
0:01.62 - the amount of elapsed real time that your command took to complete. Note that this is not the sum of user mode and kernel mode CPU times. The .097 is the time spent for things like interprocess communication, scheduling times, etc...
93.2% - The percentage of CPU time this process got. (1.189+.334)/1.62
0+0k -Average shared text space use by your process plus average size of unshared data which gives the max size which was resident in memory.
0+0io - number of file system input and number of file system outputs.
0pf - Number of page faults both where a read/write had to come from disk and or unclaimed virtual memory pages.
+0w - Number of times the proccess was swapped out of main memory." [Kevin Cleary via email]

Erlang uses a different model of concurrency from Python's. Erlang does not support shared memory, but uses a message-passing model, using

Erlang variables are single assignment, and most Erlang data structures are immutable. One of the few mutable data structures is the Erlang Term Storage (ETS).

Combining these ideas, the Erlang version of the counter program is

-module(processCount).
-export([counterResource/0,count/3,processCount/0]).
-import(ets,[]).

counterResource() ->
    receive
	initialize ->
	    io:fwrite("Initializing countTable.~n"),
	    ets:new(countTable,[public,named_table]),
	    ets:insert(countTable,{count,0}),
	    counterResource();
	{increment,Delta,Caller} ->
	    NewVal = ets:lookup_element(countTable,count,2)+Delta,
	    io:fwrite("~w is incrementing counter to: ~w~n", [Caller,NewVal]),
	    ets:insert(countTable,{count, NewVal}),
	    counterResource();
	{stop, Caller}  ->
	    Caller!{done, ets:lookup_element(countTable,count,2)},
	    counterResource();
	finished -> true
    end.


count(Counter,0,Sender) ->
    Counter!{stop, self()},
    receive {done,Count} -> true end,
    io:fwrite("~w is done counting.~n",[self()]),
    Sender!{done,Count};

count(Counter,Max,Sender) ->
    Counter!{increment,1,self()},
    count(Counter,Max-1,Sender).
	
processCount() ->
    Max = 100,
    io:fwrite("Starting processCount.erl ~w~n",[self()]),

    Counter = spawn(processCount,counterResource,[]),

    Counter!initialize,

    io:fwrite("Spawning ~w,~n",
	      [spawn(processCount,count,[Counter,Max,self()])]),
    io:fwrite("Spawning ~w,~n",
	      [spawn(processCount,count,[Counter,Max,self()])]),
    io:fwrite("Spawning ~w,~n",
	      [spawn(processCount,count,[Counter,Max,self()])]),

    receive {done,_} -> true end,
    receive {done,_} -> true end,
    receive {done,FinalCount} -> true end,

    Counter!finished,
    io:fwrite("Total count is ~w~n", [FinalCount]),
    halt().
This can be run by doing
erl -compile /projects/shapiro/CSE305/ConcurrentPrograms/processCount.erl
erl -noshell -s processCount processCount

First Previous Next

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

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