Today's plan
- inter-process communication
- races
- pipes
- semaphores
- monitors
- message passing
- read-copy-update (RCU)
Inter-process Communication
- processes may depend on each other
- example: a consumer may wait for the producer to produce, or a producer
may wait for the consumer to consume and free up space in the shared buffer
- IPC mechanisms may support one or more of the following:
- synchronization: process A can tell when process B has reached a
certain point. Example: barrier synchronization stops all processes
in a group until the last one has arrived at the barrier
- data exchange: process A receives data from process B. May
involve synchronization so process A waits for process B to complete
preparing the data
- mutual exclusion (a special case of synchronization):
at most one process may be accessing a certain set of variables. If
a process expects to be the only one accessing a given set of variables,
that process is in its critical region
Race Conditions
- incrementing is not an atomic operation
- if:
- one process (A) begins to increment a memory location by reading
a value into a register
- then A gets suspended, then
- another process (B) increments the memory location, then
- the first process writes the (now) incorrect value back to memory
the final result should be n+2, but is only n+1
- other non-atomic operations include inserting a value into
a data structure (e.g. adding a file to a print spool or adding a
value to a queue) or writing contents to a file
Avoiding Race Conditions
- prevent execution of any other process while process A is in
its critical region:
- by disabling interrupts, or
- by requiring all code to acquire a mutual exclusion lock
before entering a critical region, and release it at the end of the
critical region, or
- by requiring all code to acquire a binary semaphore
- code that cannot acquire a lock must spin, that is, loop
- code that cannot acquire (down) a semaphore will be put to
sleep (suspended), and woken up once the semaphore is released (uped)
- setting locks or semaphores generally requires atomic operations:
- interrupts can be disabled while the lock or semaphore is accessed, or
- hardware atomic operations such as Test-And-Set instructions can
perform the operation while guaranteeing atomicity, even in the case
of multiple processors
- more complex software-only solutions are available as well,
including Peterson's algorithm
- semaphores are much more general, allowing up to n acquire
operations. This can allow semaphores to be used, for example, to keep
track of the number of items (or free spaces) in a buffer.
Reader and Writer Locks
- some data structures have reader processes and
writer processes
- any data structure could have any number of readers if there were
no writers
- a read lock can be acquired any number of times, as long as there
are no writers
- the corresponding write lock can only be acquired if there are no
other readers or writers
- as an alternative:
- writers increment a counter when they enter and exit their
critical section
- writers only enter if the counter was even
- readers record the value of the counter at the beginning of the
critical section, and only enter if the counter is even
- readers repeat their critical section if the counter has changed
by the end of the critical section
- very efficient for writers, can be very slow (livelock) for readers
Pipes
- ideal for consumer-producer problem
- on Unix/Posix: pipes of bytes. In theory pipes of arbitrary
data structures could also be useful
- on Unix/Posix can use arbitrary file operations, including
read and write, select, and close.
- consumer reads from pipe, up to a maximum specified by the
read call, may block if no data is available
- producer writes to pipe, may block if no buffer space is available
- in a system with very little memory, could synchronize
producer and consumer to copy data from producer's buffer directly to
consumer's buffer
- in practice performance increases with a system buffer,
so that the producer can write a certain amount of data (typically 4096 bytes)
and consumer can read large blocks of data
- a single process that is both producer and consumer of the same
pipe will block if the system suspends the consumer until the buffer is full
Monitors
- a monitor is a module (similar to a C++/Java class) with some
private variables that only the monitor routines can access
- at most one monitor routine can be active at any given time
- when a second monitor routine is called, it suspends before
executing until the first has completed
- a monitor routine never needs to be re-entrant, that is,
it is guaranteed to complete before the next call to the same routine
(or any other routine in the same monitor)
- a monitor routine that cannot continue can wait, and another
is allowed to run (and eventually signal the one that called wait)
- writing re-entrant code is harder (and errors are harder to debug),
so monitors help write correct concurrent programs
- unfortunately, monitors require language support
- instead, in practice programmers must
- identify related global variables that are changed in individual
critical regions
- create a lock associated with each group of variables
- acquire the lock each time before reading or modifying the variables
- release the lock each time after reading or modifying the variables
- these operations are error-prone
- failure to release a lock may deadlock the entire system, since all
processes may eventually be waiting for the lock to be released
- failure to release a lock is easy -- just return
from within the critical region
Message Passing
- can extend pipes to pass structured data rather than just bytes
- can also extend pipes to data among different processors or
across a network
- the result is called message passing
- message passing systems must answer questions of:
- identifying receiver, e.g. a process or a mailbox (a mailbox could
be accessed via a file descriptor, e.g. a socket)
- dealing with lost messages or overflowing buffers: acknowledgements,
sequence numbers
- efficiency, avoiding copying unless absolutely necessary --
important on message-passing supercomputers
- message passing may not provide enough synchronization, so
external forms of synchronization, e.g. barrier synchronization,
can be used
- another form of synchronization is to have the sender
rendezvouz with the receiver so data can be copied
directly from the sender's to the receiver's buffer
Read-Copy-Update (RCU)
- some data structures have reader processes and
writer processes
- any data structure could have any number of readers if there were
no writers
- instead of having reader/writer locks,
- readers could simply notify the system that they are reading, and
when they are done reading
- writers could
- create a copy of part of the data structure
- wait for all the readers to finish reading
- destructively update the data structure to point to the new copy
- this is called RCU, and is very efficient for readers