Introduction to Concurrency
Definitions
The Concrete
- Program
- The source code for a process or processes.
- Process
- A unit of program execution as seen by an operating system.
Processes tend to act like they are the only thing
running. A process has its own address space, file
handles, security attributes, threads, etc.
The operating system prevents processes from messing
with each other.
- Thread
- A sequential flow of control within a process.
A process can contain one or more threads.
Threads have their own program counter and register
values, but they share the memory space and
other resources of the process.
- Multiprogramming/Multiprocessing
- Concurrent execution of several programs on one computer.
- Multithreading
- Execution of a program with multiple threads.
The Abstract
- Concurrency
- The state of several instruction sequences
executing at the same time (in real or abstract parallelism).
These "threads of control" can communicate via shared memory or
message passing.
- Distribution
- Concurrency in which all communication is via message passing
(useful because shared memory communication doesn't scale
to thousands of processors).
The Pragmatic
- Concurrent Programming
- Solving a single problem by breaking it down into concurrently
executing processes or threads.
- Shared Resource
- An entity used by more than one thread but one that (usually)
can only be operated on by one thread at a time.
- Synchronization
- The act of threads agreeing to coordinate their behavior
in order to solve a problem correctly. (This usually means
that thread A will block and wait until thread B performs
some operation.)
Why Study Concurrency?
- Real world systems are naturally concurrent, and computer science is
about modeling the real world.
- Concurrency is useful in multiprocessor and distributed
computer systems.
- Increased performance from true parallelism
- Increased reliability (fault tolerance)
- Specialized processors (graphics, communication, encryption ...)
- Some applications, like email, are inherently distributed
- Concurrent programming often results in superior program
structure: write code for the different tasks and let some
separate engine schedule the tasks.
Example: It's nice when writing
code to mine data, analyze telemetry, write massive files to disk, or
produce frames for a movie, to not have to chunk up your code and
shove in checks for the keyboard and mouse and other devices.
Example: When writing
a word processor, it's a lot simpler to write separate threads for
- reading keystrokes and collecting characters into words
- collecting words to fill a line
- hyphenate where necessary
- add spacing to justify with right margin
- insert page breaks where necessary
- check spelling
- save (periodically)
(
One difficulty in doing this sequentially is that "hyphenation
requires a portion of the word to be returned to the stream
of words to await the next line" [Ben-Ari, 1990].)
Examples of Concurrency
- Gardening (one rakes, one blows, one mows, one plants flowers...)
- Railway Networks (lots of trains running, but that have to synchronize
on shared sections of track)
- Parts in a machine
- Machines in a factory
- Your plain old Operating System, with lots of processes
running concurrently.
- Ancient "time-sharing" systems
- Multiprogramming
- Sitting at your workstation while
- downloading a file
- listening to streaming audio
- having a clock running
- carrying on IM conversations
- analyzing data from some weird experiment
- sending a fax
- printing something
- running a simulation
- typing in a word processor or text editor
- running a web server, sendmail, and other daemons
- Simulation programs
- Realtime and embedded systems
- Expression Evaluation - for example
(ab+cd2)(g+fh) can be evaluated
in four clocks:

- Parallelizing Statements - most supercomputers can automatically
detect this.
-- Each of the K processors or execution units can work on the
-- code below for a specific value for I, so the loop can
-- be executed in ceil(N/K) "steps".
for I in 1..N loop
D[I] := A[I] + B[I] * C[I]
end loop
- Pipes - for example
sort | uniq | format | print
- Parallelizable algorithms: Mergesort, Quicksort, Summing a list by
summing fragments in parallel, computing primes, checking equality
of leaf sequences in trees, computing n-choose-k, many others.
- Word processor implementation (have you been paying attention?)
- Banking systems
- Travel reservation systems
- High volume web servers (or any kind of server, really: multiple
clients are serviced at the same time to give the appearance
of responsiveness)
- Grid computing systems
- Multiplayer games
Is this stuff hard?
Well, yeah. Concurrent programming is harder than sequential programming
because concurrent programming is about writing a bunch of communicating
sequential programs. You end up with a bunch of problems that don't
exist in sequential programs:
- How do you get concurrent threads to synchronize
when necessary so they don't make a mess of shared
resources?
- How do you avoid race conditions,
deadlock, and starvation?
- How do you ensure safety and
liveness?
- How do you prove these nasty things partially
correct? Totally correct?
- How do you partition the code into threads, or partition the
processes on processors so that everything runs
efficiently?
- How do you recover if one of the nodes in a distributed system
fails? (Fault tolerance)
Concerns
The field of concurrent programming is concerned with
- Modeling
- Granularity
- Scheduling
- Communication
- Synchronization
- Language Integration
- Implementation
Modeling
We need a formal way to talk about concurrent programming
so that we can analyze requirements and design and implement
correct and efficient algorithms. One of the most useful models
used in reasoning about concurrent programs is the non-realtime
interleaved execution model. This is:
The study of interleaved execution sequences of
atomic instructions, where each of the instructions
execute in a completely arbitrary but finite amount
of time.
In this model we can make no assumptions at all about
the relative speeds of the individual instructions, or how
malicious or anal a scheduler might be. Since instructions take
arbitrary time, there can be a lot of possible interleavings.
Example: Suppose thread-1 has instruction sequence
[A B C] and thread-2 has sequence [x y]. Then we have to consider:
[A B C x y]
[A B x C y]
[A B x y C]
[A x B C y]
[A x B y C]
[A x y B C]
[x A B C y]
[x A B y C]
[x A y B C]
[x y A B C]
Exercise:
Write out all interleavings of two threads, the first
with sequence [A B C] and the second with [x y z].
The number of interleavings for two threads, one with
m instructions and one with
n instructions is
(m+n)n
— which
you probably know is
(n+m)!/n!m!.
Exercise:
How many interleavings are possible with n threads
where thread i has ki instructions?
Granularity
Systems can be classified as to the level of concurrency
that can be expressed or implemented.
Instruction Level
Most processors have several execution units and can execute
several instructions at the same time. Good compilers can
reorder instructions to maximize instruction throughput.
Often the processor itself can do this.
Example: On the old
Pentium microprocessor, the instruction sequence
inc ebx
inc ecx
inc edx
mov esi,[x]
mov eax,[ebx]
would be executed as follows:
Step U-pipe V-pipe
-----------------------------------------
0 inc ebx inc ecx
1 inc edx mov esi, [x]
2 mov eax, [ebx]
Modern processors even parallelize execution of micro-steps of
instructions within the same pipe.
Statement Level
Many programming languages have syntactic forms to express
that statements should execute in sequence or in parallel.
Common notations:
| Pascal-style |
Occam-style |
Algebraic |
begin
A;
cobegin
B;
C;
coend
D;
cobegin
E;
F;
G;
coend
end
|
SEQ
a
PAR
b
c
d
PAR
e
f
g
|
a ; b || c ; d ; e || f || g
|
Procedure Level
Like threads in Java or Perl, tasks in Java, native Win32
threads, or most threading libraries like pthreads. Threads
are spawned to run a function or method.
Program Level
Usually the responsibility of the operating system,
which runs processes concurrently.
Most programming languages give you the ability to spawn
processes from your program.
In Java
Runtime.getRuntime().exec(commandline);
In C, using the Win32 API
CreateProcess(security_attributes, stack_size, &function, param, activation,
&thread_id);
Exercise:
Show how to spawn a new process using the Unix system calls
execve with fork.)
Scheduling
Generally you'll have M processors and N threads. If M < N
you need a scheduler to interleave execution.
A thread can be in one of several states; one example is:

(Note: Win32 threads have six states; we'll see them later.)
Communication
The threads of control must communicate with each other. The two main
ways to communicate:
- Shared Memory (Indirect): good for multiple threads within
the same process. (Separate processes have to simulate shared
memory via files or some other structure.)
- Message Passing (Direct): good for separate processes on the
same or different nodes, though of course one can implement
messages between threads as well.
Synchronization
Threads sometimes have to coordinate their activities. Suppose
two threads A and B are trying to make a $100 deposit. They both execute
the sequence:
- move the current value of the account into a register
- add 100 to the register
- write the value of the register back into memory
If A executes its first statement and then B executes its first
statement before A finishes its third statement, one of the deposits
gets lost. There are dozens of programming paradigms to make sure threads
can synchronize properly to avoid these problems.
Support for Concurrency
Many have argued whether a language should have direct
support for concurrency and distribution or whether such
support should come from a library.
| Library or O.S. based |
Language-Intrinsic |
.
.
.
int t1, t2;
.
.
.
t1 = createThread(myFunction);
.
.
.
start(t1);
// now t1 runs in parallel
// with the function that
// called start().
// Termination is
// asynchronous, though
// joining can be done with
// a library call.
|
procedure P is
.
.
.
task T;
.
.
.
begin -- T activated implicitly here
.
.
. -- T and P execute in parallel
.
.
end P; -- T and P terminate together
|
|
Advantages:
- Because different languages have different models
of concurrency, language interfacing (multi-lingual
development) may be easier.
- A specific language model may not fit well
with a particular O.S.
- O.S. standards (e.g. POSIX) exist anyway,
so perhaps portability might be likely.
|
Advantages:
- Code likely to be more reliable and maintainable
since constructs are high level.
- Lots of different operating systems exist,
so code may be more portable.
- An embedded computer might not even have
an operating system.
|
Correctness
Concurrent programs have to be correct for all possible
interleavings. Natrually this makes testing more complicated,
but it can be done.