Presentation is loading. Please wait.

Presentation is loading. Please wait.

CS703 – Advanced Operating Systems

Similar presentations


Presentation on theme: "CS703 – Advanced Operating Systems"— Presentation transcript:

1 CS703 – Advanced Operating Systems
By Mr. Farhan Zaidi

2 Lecture No. 9

3 Overview of today’s lecture
Shared variable analysis in multi-threaded programs Concurrency and synchronization Critical sections Solutions to the critical section problem Concurrency examples Re-cap of lecture

4 Shared Variables in Threaded C Programs
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?

5 Threads Memory Model Each thread runs in the context of a process.
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.

6 What resources are shared?
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

7 Synchronization to share resources, access shared data structures
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 network writer thread disk reader thread circular buffer

8 scheduling is not under application writers’ control
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

9 Example of Threads Accessing Another Thread’s Stack
char **ptr; /* global */ int main() { int i; pthread_t tid; char *msgs[2] = { "Hello from foo", "Hello from bar" }; ptr = msgs; for (i = 0; i < 2; i++) Pthread_create(&tid, NULL, thread, (void *)i); Pthread_exit(NULL); } /* thread routine */ void *thread(void *vargp) { 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

10 Mapping Variables to Mem. Instances
Global var: 1 instance (ptr [data]) Local automatic vars: 1 instance (i.m, msgs.m ) char **ptr; /* global */ int main() { int i; pthread_t tid; char *msgs[N] = { "Hello from foo", "Hello from bar" }; ptr = msgs; for (i = 0; i < 2; i++) Pthread_create(&tid, NULL, thread, (void *)i); Pthread_exit(NULL); } Local automatic var: 2 instances ( myid.p0[peer thread 0’s stack], myid.p1[peer thread 1’s stack] ) /* thread routine */ void *thread(void *vargp) { int myid = (int)vargp; static int svar = 0; printf("[%d]: %s (svar=%d)\n", myid, ptr[myid], ++svar); } Local static var: 1 instance (svar [data])

11 Shared Variable Analysis
Which variables are shared? Variable Referenced by Referenced by Referenced by instance main thread? peer thread 0? peer thread 1? ptr yes yes yes svar no yes yes i.m yes no no msgs.m yes yes yes myid.p0 no yes no myid.p1 no no yes A variable x is shared iff multiple threads reference at least one instance of x. Thus: ptr, svar, and msgs are shared. i and myid are NOT shared.

12 badcnt.c: An Improperly Synchronized Threaded Program
unsigned int cnt = 0; /* shared */ #define NITERS int main() { pthread_t tid1, tid2; Pthread_create(&tid1, NULL, count, NULL); Pthread_create(&tid2, NULL, Pthread_join(tid1, NULL); Pthread_join(tid2, NULL); if (cnt != (unsigned)NITERS*2) printf("BOOM! cnt=%d\n", cnt); else printf("OK cnt=%d\n", } /* thread routine */ void *count(void *arg) { int i; for (i=0; i<NITERS; i++) cnt++; return NULL; } linux> ./badcnt BOOM! cnt= BOOM! cnt= BOOM! cnt= cnt should be equal to 200,000,000. What went wrong?!

13 Assembly Code for Counter Loop
C code for counter loop Corresponding asm code (gcc -O0 -fforce-mem) for (i=0; i<NITERS; i++) cnt++; .L9: movl -4(%ebp),%eax cmpl $ ,%eax jle .L12 jmp .L10 .L12: movl cnt,%eax # Load leal 1(%eax),%edx # Update movl %edx,cnt # Store .L11: leal 1(%eax),%edx movl %edx,-4(%ebp) jmp .L9 .L10: Head (Hi) Load cnt (Li) Update cnt (Ui) Store cnt (Si) Tail (Ti)

14 Concurrent Execution Ii denotes that thread i executes instruction I
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 i (thread) instri %eax1 %eax2 cnt 1 H1 - - 1 L1 - 1 U1 1 - 1 S1 1 - 1 2 H2 - - 1 2 L2 - 1 1 2 U2 - 2 1 2 S2 - 2 2 2 T2 - 2 2 1 T1 1 - 2 OK

15 Concurrent Execution (cont)
Incorrect ordering: two threads increment the counter, but the result is 1 instead of 2. i (thread) instri %eax1 %eax2 cnt 1 H1 - - 1 L1 - 1 U1 1 - 2 H2 - - 2 L2 - 1 S1 1 - 1 1 T1 1 - 1 2 U2 - 1 1 2 S2 - 1 1 2 T2 - 1 1 Oops!

16 Concurrent Execution (cont)
How about this ordering? i (thread) instri %eax1 %eax2 cnt 1 H1 1 L1 2 H2 2 L2 2 U2 2 S2 1 U1 1 S1 1 T1 2 T2 We can clarify our understanding of concurrent execution with the help of the progress graph

17 Progress Graphs A 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 (Inst1, Inst2). E.g., (L1, S2) denotes state where thread 1 has completed L1 and thread 2 has completed S2. Thread 2 T2 (L1, S2) S2 U2 L2 H2 Thread 1 H1 L1 U1 S1 T1

18 Trajectories in Progress Graphs
Thread 2 A 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 T2 S2 U2 L2 H2 Thread 1 H1 L1 U1 S1 T1

19 Critical Sections and Unsafe Regions
Thread 2 L, 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. T2 S2 critical section wrt cnt U2 Unsafe region L2 H2 Thread 1 H1 L1 U1 S1 T1 critical section wrt cnt

20 Safe and Unsafe Trajectories
Thread 2 Def: 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. Safe trajectory T2 S2 Unsafe region Unsafe trajectory critical section wrt cnt U2 L2 H2 Thread 1 H1 L1 U1 S1 T1 critical section wrt cnt

21 The classic example int withdraw(account, amount) {
Suppose we have to implement a function to withdraw money from a bank account: int withdraw(account, amount) { int balance = get_balance(account); balance -= amount; put_balance(account, balance); return balance; } Now suppose a husband and wife share a bank account with a balance of $100.00 what happens if you both go to separate ATM machines, and simultaneously withdraw $10.00 from the account?

22 have both threads run on the same bank mainframe:
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); balance -= amount; put_balance(account, balance); return balance; }

23 Interleaved schedules
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? balance = get_balance(account); balance -= amount; put_balance(account, balance); Execution sequence as seen by CPU context switch

24 Race conditions and concurrency
Atomic 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. Many instructions are not atomic. For example, on most 32-bit architectures, double precision floating point store is not atomic; it involves two separate memory operations.

25 The crux of the problem creates a race condition
The problem is that two concurrent threads (or processes) access a shared resource (account) without any synchronization creates a race condition output is non-deterministic, depends on timing We need mechanisms for controlling access to shared resources in the face of concurrency so we can reason about the operation of programs essentially, re-introducing determinism Synchronization is necessary for any shared data structure buffers, queues, lists, hash tables, scalars, …

26 Synchronization related definitions
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

27 Critical section solution requirements
Critical sections have the following requirements mutual exclusion at most one thread is in the critical section progress if thread T is outside the critical section, then T cannot prevent thread S from entering the critical section 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

28 Synchronization: Too Much Milk
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. 3: Buy milk. 3: Arrive home, put milk in fridge. Oops!! Too much milk

29 Too much milk: Solution 1
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. Solution #1: if (noMilk) { if (noNote){ leave Note; buy milk; remove note; } Why doesn't this work?

30 Too much milk: Solution 2
Actually, 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) { if (noMilk) if(noMilk) buy Milk buy Milk; } } Remove Note A remove Note B


Download ppt "CS703 – Advanced Operating Systems"

Similar presentations


Ads by Google