Advanced Operating Systems - Lecture 9: Shared variables. This lecture will cover the following: shared variable analysis in multi-threaded programs; concurrency and synchronization; critical sections; solutions to the critical section problem; concurrency examples;...
Trang 1CS703 – Advanced Operating Systems
By Mr Farhan Zaidi
Trang 2Lecture No. 9
Trang 4 Question: Which variables in a threaded C program are shared variables?
The answer is not as simple as “global variables are
shared” and “stack variables are private”.
Requires answers to the following questions:
What is the memory model for threads?
How are variables mapped to memory instances?
How many threads reference each of these instances?
Trang 5 Conceptual model:
Each thread runs in the context of a process
Each thread has its own separate thread context
Thread ID, stack, stack pointer, program counter, condition codes, and general purpose registers
All threads share the remaining process context
Code, data, heap, and shared library segments of the process virtual address space
Open files and installed handlers
Operationally, this model is not strictly enforced:
While register values are truly separate and protected
Any thread can read and write the stack of any other thread
Mismatch between the conceptual and operation model is a source of confusion and errors.
Trang 6 Local variables are not shared
refer to data on the stack, each thread has its own stack
never pass/share/store a pointer to a local variable on another
thread’s stack!
Global variables are shared
stored in the static data segment, accessible by any thread
Dynamic objects are shared
stored in the heap, shared if you can name it
in C, can conjure up the pointer
e.g., void *x = (void *) 0xDEADBEEF
in Java, strong typing prevents this
must pass references explicitly
Trang 7 Threads cooperate in multithreaded programs
to share resources, access shared data structures
e.g., threads accessing a memory cache in a web server
also, to coordinate their execution
e.g., a disk reader thread hands off blocks to a network writer thread through a circular buffer
disk reader thread
network writer thread circular buffer
Trang 8 For correctness, we have to control this cooperation
must assume threads interleave executions arbitrarily and at different rates
scheduling is not under application writers’ control
We control cooperation using synchronization
enables us to restrict the interleaving of executions
Note: this also applies to processes, not just threads
It also applies across machines in a distributed system
Trang 9"Hello from foo",
"Hello from bar"
int myid = (int)vargp;
static int svar = 0;
printf("[%d]: %s (svar=%d)\n", myid, ptr[myid], ++svar); }
Peer threads access main thread’s stack indirectly through global ptr variable
Trang 10"Hello from foo",
"Hello from bar"
int myid = (int)vargp;
static int svar = 0;
printf("[%d]: %s (svar=%d)\n", myid, ptr[myid], ++svar); }
Global var: 1 instance (ptr [data])
Local static var: 1 instance (svar [data])
Local automatic vars: 1 instance (i.m, msgs.m )
Local automatic var: 2 instances (
myid.p0 [peer thread 0’s stack],
)
Trang 11 Variable Which variables are shared?Referenced by Referenced by Referenced by
instance main thread? peer thread 0? peer thread 1?
ptr, svar, and msgs are shared.
i and myid are NOT shared.
Trang 12linux> /badcnt BOOM! cnt=198261801
linux> /badcnt BOOM! cnt=198269672cnt should be equal to 200,000,000 What went wrong?!
Trang 13.L9:
movl -4(%ebp),%eax cmpl $99999999,%eax jle L12
jmp L10 L12:
movl cnt,%eax # Load leal 1(%eax),%edx # Update movl %edx,cnt # Store L11:
movl -4(%ebp),%eax leal 1(%eax),%edx movl %edx,-4(%ebp) jmp L9
Trang 14 Key idea: In general, any sequentially consistent interleaving is possible, but some are incorrect!
Ii denotes that thread i executes instruction I
%eaxi is the contents of %eax in thread i’s context
0 1 1 - - - 1
-0 0 0 1 1 1 1 2 2 2
i (thread) instr i %eax 1 cnt
OK
- - - - 1 2 2 2 -
-%eax 2
Trang 150 1 - - 1 1 - - -
-0 0 0 0 0 1 1 1 1 1
i (thread) instr i %eax 1 cnt
- - - 0 - - 1 1 1
-%eax 2
Oops!
Trang 16i (thread) instr i %eax 1 %eax 2 cnt
We can clarify our understanding of concurrent execution with the help of the progress graph
Trang 17A progress graph depicts
the discrete execution
state space of concurrent
threads.
Each axis corresponds to the sequential order of instructions in a thread.
Each point corresponds to
a possible execution state
(L 1 , S 2 )
Trang 18A trajectory is a sequence
of legal state transitions that describes one possible concurrent execution of
the threads.
Example:
H1, L1, U1, H2, L2, S1, T1, U2, S2, T2
Trang 19L, U, and S form a
critical section with
respect to the shared variable cnt.
Instructions in critical sections (wrt to some shared variable) should not be interleaved.
Sets of states where such interleaving occurs
form unsafe regions.
Trang 20Def: A trajectory is safe
iff it doesn’t touch any part of an unsafe region.
Claim: A trajectory is
correct (wrt cnt ) iff it is safe.
critical section wrt cnt
critical
section
wrt cnt
Trang 21 Suppose we have to implement a function to withdraw money from a bank account:
int withdraw(account, amount) {
int balance = get_balance(account);
Trang 22 Represent the situation by creating a separate thread for each person to
do the withdrawals
have both threads run on the same bank mainframe:
int withdraw(account, amount) {
int balance = get_balance(account);
Trang 23 The problem is that the execution of the two threads can be interleaved, assuming preemptive scheduling:
What’s the account balance after this sequence?
who’s happy, the bank or you?
How often is this unfortunate sequence likely to occur?
Trang 24Atomic operation: operation always runs to completion, or
not at all Indivisible, can't be stopped in the middle
On most machines, memory reference and assignment
(load and store) of words, are atomic.
architectures, double precision floating point store is not atomic;
it involves two separate memory operations
Trang 25
creates a race condition
output is non-deterministic, depends on timing
in the face of concurrency
so we can reason about the operation of programs
essentially, re-introducing determinism
buffers, queues, lists, hash tables, scalars, …
Trang 26 Mutual exclusion: ensuring that only one thread
does a particular thing at a time One thread doing it
excludes all others.
Critical section: piece of code that only one thread
can execute at one time All other threads are forced
to wait on entry when a thread leaves a critical
section, another can enter Mutual exclusion is
required inside the critical section.
Key idea all synchronization involves waiting
Trang 27 bounded waiting (no starvation)
if thread T is waiting on the critical section, then T will
eventually enter the critical section
assumes threads eventually leave critical sections
performance
the overhead of entering and exiting the critical section is
small with respect to the work being done within it
Trang 28 Person A Person B
3:00 Look in fridge Out of milk.
3:05 Leave for store.
3:10 Arrive at store Look in fridge Out of milk.
3:15 Buy milk Leave for store.
3:20 Arrive home, put milk in fridge Arrive at store.
Trang 29 What are the correctness properties for the too much milk problem?
Never more than one person buys; someone buys if needed
Restrict ourselves to only use atomic load and store operations as building blocks.
Trang 30Actually, solution 1 makes problem worse fails only occasionally Makes it really hard to debug Remember, constraint has to be satisfied, independent of what the dispatcher does timer can go off, and context switch can happen at any time.
Solution #2:
Thread A Thread B
leave note A leave note B
if (no Note from B) if (no Note from A)