Presentation is loading. Please wait.

Presentation is loading. Please wait.

CENG 334 – Operating Systems 03- Threads & Synchronization

Similar presentations


Presentation on theme: "CENG 334 – Operating Systems 03- Threads & Synchronization"— Presentation transcript:

1 CENG 334 – Operating Systems 03- Threads & Synchronization
Asst. Prof. Yusuf Sahillioğlu Computer Eng. Dept, , Turkey

2 Threads Threads. Concurrent work on the same process for efficiency.
Several activities going on as part of the same process. Share registers, memory, and other resources. All about data synchronization:

3 Concurrency vs. Parallelism
Concurrency: 2 processes or threads run concurrently (are concurrent) if their flows overlap in time Otherwise, they are sequential. Examples (running on single core): Concurrent: A & B, A & C Sequential: B & C Parallelism: requires multiple resources to execute multiple processes or threads at a given time instant. A&B parallel:

4 Concurrent Programming
Many programs want to do many things “at once” Web browser: Download web pages, read cache files, accept user input, ... Web server: Handle incoming connections from multiple clients at once Scientific programs: Process different parts of a data set on different CPUs In each case, would like to share memory across these activities Web browser: Share buffer for HTML page and inlined images Web server: Share memory cache of recently-accessed pages Scientific programs: Share memory of data set being processes Can't we simply do this with multiple processes?

5 Why processes are not always ideal?
Processes are not very efficient Each process has its own PCB and OS resources Typically high overhead for each process: e.g., 1.7 KB per task_struct on Linux! Creating a new process is often very expensive Processes don't (directly) share memory Each process has its own address space Parallel and concurrent programs often want to directly manipulate the same memory e.g., When processing elements of a large array in parallel Note: Many OS's provide some form of inter-process shared memo e.g., UNIX shmget() and shmat() system calls Still, this requires more programmer work and does not address the efficiency issues.

6 Can we do better? What can we share across all of these tasks?
Same code – generally running the same or similar programs Same data Same privileges Same OS resources (files, sockets, etc.)‏ What is private to each task? Execution state: CPU registers, stack, and program counter Key idea of this lecture: Separate the concept of a process from a thread of control The process is the address space and OS resources Each thread has its own CPU execution state

7 Threads vs. Processes Processes form a tree hierarchy.
Threads form a pool of peers. Each thread can kill any other. Each thread can wait for any other thread to terminate. Main thread: first thread to run in a process. Process hierarchy Thread pool P0 T2 T4 T1 P1 shared code, data and kernel context sh sh sh T3 T5 foo

8 Threads vs. Processes Each process has one or more threads “within” it
Each thread has its own stack, CPU registers, etc. All threads within a process share the same address space and OS resources Threads share memory, so they can communicate directly! The thread is now the unit of CPU scheduling A process is just a “container” for its threads Each thread is bound to its containing process Thread 0 Thread 2 Thread 1 Address space

9 (Old) Process Address Space
0xFFFFFFFF (Reserved for OS)‏ Stack Stack pointer Address space Heap Uninitialized vars (BSS segment)‏ Initialized vars (data segment)‏ Code (text segment)‏ Program counter 0x

10 (New) Process Address Space w/ Threads

11 Implementing Threads Given what we know about processes, implementing threads is “easy” Idea: Break the PCB into two pieces: Thread-specific stuff: Processor state Process-specific stuff: Address space and OS resources (open files, etc.)

12 Thread Control Block (TCB)
TCB contains info on a single thread Just processor state and pointer to corresponding PCB PCB contains information on the containing process Address space and OS resources ... but NO processor state!

13 Thread Control Block (TCB)
TCB's are smaller and cheaper than processes Linux TCB (thread_struct) has 24 fields Linux PCB (task_struct) has 106 fields Hence context switching threads is cheaper than context switching processes.

14 Context Switching TCB is now the unit of a context switch
Ready queue, wait queues, etc. now contain pointers to TCB's Context switch causes CPU state to be copied to/from the TCB Context switch between two threads in the same process: No need to change address space Context switch between two threads in different processes: Must change address space, sometimes invalidating cache This will become relevant when we talk about virtual memory.

15 Thread State State shared by all threads in process:
Memory content (global variables, heap, code, etc). I/O (files, network connections, etc). A change in the global variable will be seen by all other threads (unlike processes). State private to each thread: Kept in TCB (Thread Control Block). CPU registers, program counter. Stack (what functions it is calling, parameters, local variables, return addresses). Pointer to enclosing process (PCB).

16 Thread Behavior Some useful applications with threads:
One thread listens to connections; others handle page requests. One thread handles GUI; other computations. One thread paints the left part, other the right part. ..

17 Thread Behavior Single threaded Multi-threaded main()
computePI(); //never finish printf(“hi”); //never reach here A process has a single thread of control: if it blocks on something nothing else can be done. Multi-threaded createThread( computePI() ); //never finish createThread( printf(“hi”) ); //reaches here createThread( scanf() ); //not finish ‘till user enters (not in CPU) createThread( autoSaveDoc() ); //reaches here while waiting on I/O

18 Thread Behavior Execution flow:

19 Threads on a Single CPU Still possible. Multitasking idea
Share one CPU among many processes (context switch). Multithreading idea Share the same process among many threads (thread switch). Whenever this process has the opportunity to run in the CPU, OS can select one of its many threads to run it for a while, and so on. One pid, several thread ids. Schedulable entities increased.

20 Threads on a Single CPU If threads are all CPU-bound, e.g., no I/O or pure math, then we do not gain much by multithreading. Luckily this is usually not the case, e.g., 1 thread does the I/O, .. Select your threads carefully, one is I/O-bound, other is CPU-bound, .. With multicores, we still gain big even if threads are all CPU-bound.

21 Multithreading Concept
Multithreading concept: pseudo-parallel runs. (pseudo: interleaving switches on 1 CPU). funct1() { .. } funct2() { .. } main() { .. createThread( funct1() ); createThread( funct2() ); } thread1 thread2 thread3 thread4

22 Single- vs. Multi-threaded Processes
Shared and private stuff:

23 Benefits of Threads Responsiveness Resources sharing Economy
One thread blocks, another runs. One thread may always wait for the user. Resources sharing Very easy sharing (use global variables; unlike msg queues, pipes, shmget). Be careful about data synchronization tough. Economy Thread creation is fast. Context switching among threads may be faster. ‘cos you do not have to duplicate code and global variables (unlike processes). Scalability Multiprocessoers can be utilized better. Process that has created 4 threads can use all 4 cores (single-threaded proc. utilize 1 core).

24 Naming Why do we call it a thread anyway?
Execution flow of a program is not smooth, looks like a thread. Execution jumps around all over the place (switches) but integrity is intact.

25 Multithreading Example: WWW
Client (Chrome) requests a page from server (amazon.com). Server gives the page name to the thread and resumes listening. Thread checks the disk cache in memo; if page not there, do disk I/O; sends the page to the client.

26 Threading Support User-level threads: are threads that the OS is not aware of. They exist entirely within a process, and are scheduled to run within that process's time slices. Kernel-level threads: The OS is aware of kernel-level threads. Kernel threads are scheduled by the OS's scheduling algorithm, and require a "lightweight" context switch to switch between (that is, registers, PC, and SP must be changed, but the memory context remains the same among kernel threads in the same process).

27 Threading Support User-level threads are much faster to switch between, as there is no context switch; further, a problem-domain-dependent algorithm can be used to schedule among them. CPU-bound tasks with interdependent computations, or a task that will switch among threads often, might best be handled by user-level threads.

28 Threading Support Kernel-level threads are scheduled by the OS, and each thread can be granted its own time slices by the scheduling algorithm. The kernel scheduler can thus make intelligent decisions among threads, and avoid scheduling processes which consist of entirely idle threads (or I/O bound threads). A task that has multiple threads that are I/O bound, or that has many threads (and thus will benefit from the additional time slices that kernel threads will receive) might best be handled by kernel threads. Kernel-level threads require a system call for the switch to occur; user-level threads do not.

29 Threading Support Thread libraries that provide us API for creating and managing threads. pthreads, java threads, win32 threads. Pthreads (POSIX threads) interface. Common in Unix operating sytems: Solaris, Mac OS, Linux. No implemented in the standard C library; search the library named pthread while compiling: gcc –o thread1 –lpthread thread1.c Implementation-dependent; can be user- or kernel-level. Functions in pthread library are actually doing linux system calls, e.g., pthread_create()  clone() See sample codes to warm up on pthreads: also see threadster1.c

30 Pthreads thread1 thread2 int main(..) { ..
pthread_create(&tid,…,runner,..); pthread_join(tid); printf (sum); } runner (..) { sum = .. pthread_exit(); wait

31 Single- to Multi-thread Conversion
In a simple world Identify functions as parallel activities. Run them as separate threads. In real world Single-threaded programs use global variables, library functions (malloc). Be careful with them. Global variables are good for easy-communication but need special care.

32 Single- to Multi-thread Conversion
Careful with global variable:

33 Single- to Multi-thread Conversion
Careful with global variable:

34 Single- to Multi-thread Conversion
Global, local, and thread-specific variables. thread-specific: global inside the thread, but not for the whole process, i.e., other threads cannot access it, but all the functions of the thread can (no problem ‘cos fnctns within a thread executed sequentially). No language support for this variable type; C cannot do this. Thread API has special functions to create such variables.

35 Single- to Multi-thread Conversion
Use thread-safe (reentrant, reenterable) library routines. Multiple malloc()s are executed sequentially in a single-threaded code. Say one thread is suspended on malloc(); another process calls malloc() and re-enters it while the 1st one has not finished. Library functions should be designed to be reentrant = designed to have a second call to itself from the same process before it’s finished. To do so, do not use global variables.

36 Thread Issues All threads in a process share memory:
What happens when two threads access the same variable? Which value does Thread 2 see when it reads “foo”? What does it depend on? Thread 0 Thread 2 Thread 1 Address space foo write read

37 Thread Issues asd cnt should be equal to 200,000,000. What went wrong?
/* shared */ volatile unsigned int cnt = 0; //see Note section below for volatile #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", cnt);} /* thread routine */ void *count(void *arg) { int i; for (i=0; i<NITERS; i++) cnt++; return NULL; } asd linux> ./badcnt BOOM! cnt= BOOM! cnt= BOOM! cnt= Volatile: tell compiler to avoid optimization involving object x. volatile int x= 100; while (x==100)  optimized to while (true) if x never updated by this program. But another thread (or something that is unknown by the program) may update it; so tell compiler to stop optimization. cnt should be equal to 200,000,000. What went wrong?

38 Thread Issues Assembly code for counter loop:

39 Thread Issues Assembly code for counter loop.
Unpredictable switches of threads by scheduler will create inconsistencies on the shared data, e.g., global variable cnt. Handling this is arguably the most important topic of this class: Synchronization.

40 Synchronization Synchronize threads/coordinate their activities so that when you access the shared data (e.g., global variables) you are not having a trouble. Multiple processes sharing a file or shared memory segment also require synchronization (= critical section handling).

41 Synchronization Thread 3 Code Thread 1 Code Thread 2 Code Change X
The part of the process that is accessing and changing shared data is called its critical section. Thread 3 Code Thread 1 Code Thread 2 Code Change X Change X Change Y Change Y Change Y Change X Assuming X and Y are shared data.

42 Synchronization Solution: No 2 processes/threads are in their critical section at the same time, aka Mutual Exclusion (mutex). Must assume processes/threads interleave executions arbitrarily (preemptive scheduling) and at different rates. Scheduling is not under application’s control. We control coordination using data synchronization. We restrict interleaving of executions to ensure consistency. Low-level mechanism to do this: locks, High-level mechanisms: mutexes, semaphores, monitors, condition variables.

43 Synchronization General way to achieve synchronization:

44 Synchronization An example: race condition. Critical section:
critical section respected  not respected 

45 Synchronization Producer Consumer or Producer Consumer
Another example: race condition. Assume we had 5 items in the buffer. Then Assume producer just produced a new item, put it into buffer, and about to do count++ Assume consumer just retrieved an item from the buffer, and about to do count-- Producer Consumer or Producer Consumer

46 Synchronization Another example: race condition.
Critical region: is where we manipulate count. count++ could be implemented as (similarly, count--) register1 = count; //read value register1 += 1; //increase value count = register1; //write back

47 Synchronization Count register1 PRODUCER (count++) 5 6 5 4 6
Then: Count register1 PRODUCER (count++) 5 6 5 4 6 register1 = count register1 = register1 + 1 count = register1 register1 = count register1 = register1 + 1 count = register1 register2 5 4 CONSUMER (count--) register2 = count register2 = register2 – 1 count = register2 register2 = count register2 = register2 – 1 count = register2 CPU Main Memory

48 Synchronization Another example: race condition.
2 threads executing their critical section codes  Although 2 customers withdrew 100TL, balance is 900TL, not 800TL  Balance = 1000TL balance = get_balance(account); balance -= amount; Local = 900TL Execution sequence as seen by CPU balance = get_balance(account); balance -= amount; put_balance(account, balance); Local = 900TL Balance = 900TL put_balance(account, balance); Balance = 900TL!

49 Synchronization Solution: mutual exclusion. Critical Section
Only one thread at a time can execute code in their critical section. All other threads are forced to wait on entry. When one thread leaves the critical section, another can enter. Critical Section Thread 1 (modify account balance)

50 Synchronization Solution: mutual exclusion. Critical Section
Only one thread at a time can execute code in their critical section. All other threads are forced to wait on entry. When one thread leaves the critical section, another can enter. Critical Section Thread 2 Thread 1 (modify account balance) 2nd thread must wait for critical section to clear

51 Synchronization Solution: mutual exclusion. Critical Section
Only one thread at a time can execute code in their critical section. All other threads are forced to wait on entry. When one thread leaves the critical section, another can enter. Critical Section Thread 2 (modify account balance) 2nd thread free to enter 1st thread leaves critical section

52 Synchronization Solution: mutual exclusion.
pthread library provides us mutex variables to control the critical section access. pthread_mutex_lock(&myMutex) .. //critical section stuff pthread_mutex_unlock(&myMutex) See this in action here:

53 Synchronization Critical section requirements.
Mutual exclusion: at most 1 thread is currently executing in the critical section. Progress: if thread T1 is outside the critical section, then T1 cannot prevent T2 from entering the critical section. No starvation: if T1 is waiting for the critical section, it’ll eventually enter. Assuming threads eventually leave critical sections. Performance: the overhead of entering/exiting critical section is small w.r.t. the work being done within it.

54 Synchronization Solution: Peterson’s solution to mutual exclusion.
Programming at the application (sw solution; no hw or kernel support). Peterson.enter //similar to pthread_mutex_lock(&myMutex) .. //critical section stuff Peterson.exit //similar to pthread_mutex_unlock(&myMutex) Works for 2 threads/processes (not more). Is this solution OK? Set global variable lock = 1. A thread that wants to enter critical section checks lock == 1. If true, enter. Do lock--. if false, another thread decremented it so not enter.

55 Synchronization Solution: Peterson’s solution to mutual exclusion.
Programming at the application (sw solution; no hw or kernel support). Peterson.enter //similar to pthread_mutex_lock(&myMutex) .. //critical section stuff Peterson.exit //similar to pthread_mutex_unlock(&myMutex) Works for 2 threads/processes (not more). Is this solution OK? Set global variable lock = 1. A thread that wants to enter critical section checks lock == 1. If true, enter. Do lock--. if false, another thread decremented it so not enter. This solution sucks ‘cos lock itself is a shared global variable. Just using a single variable without any other protection is not enough. Back to Peterson’s algo..

56 Synchronization Solution: Peterson’s solution to mutual exclusion.
Programming at the application (sw solution; no hw or kernel support). Peterson.enter //similar to pthread_mutex_lock(&myMutex) .. //critical section stuff Peterson.exit //similar to pthread_mutex_unlock(&myMutex) Works for 2 threads/processes (not more). Assume that the LOAD and STORE machine instructions are atomic; that is, cannot be interrupted. The two processes share two variables: int turn; boolean flag[2]; The variable turn indicates whose turn it is to enter the critical section. turn = i means process Pi can execute (i=0,1). The flag array is used to indicate if a process is ready to enter the critical section. flag[i] = true implies that process Pi is ready (wants to enter).

57 Synchronization Solution: Peterson’s solution to mutual exclusion.
The variable turn indicates whose turn it is to enter the critical section. turn = i means process Pi can execute (i=0,1). The flag array is used to indicate if a process is ready to enter the critical section. flag[i] = true implies that process Pi is ready (wants to enter). Algorithm for Pi; the other process is Pj: I want to enter but, be nice to other process. Busy wait:

58 Synchronization flag[] turn Shared Variables: i=0, j=1 are local.
Solution: Peterson’s solution to mutual exclusion. PROCESS i (0) PROCESS j (1) do { flag[j] = TRUE; turn = i; while (flag[i] && turn == i); critical section.. flag[j] = FALSE; remainder section.. } while (1) do { flag[i] = TRUE; turn = j; while (flag[j] && turn == j); critical section.. flag[i] = FALSE; remainder section.. } while (1) flag[] turn Shared Variables: i=0, j=1 are local.

59 Synchronization Solution: hardware support for mutual exclusion.
Kernel code can disable clock interrupts (context/thread switches). disable interrupts (no switch) enable interrupts (schedulable)

60 Synchronization Solution: hardware support for mutual exclusion.
Works for single CPU. Multi-CPU fails ‘cos you’re disablin the interrupt only for your processor. That does not mean other processors do not get interrupts. Each processor has its own interrupt mechanism. Hence another process/thread running in another processor can touch the shared data. Too inefficient to disable interrupts on all available processors.

61 Synchronization Solution: hardware support for mutual exclusion.
Another support mechanism: Complex machine instructions from hw that are atomic (not interruptible). Locks (not just simple integers). How to implement acquire/release lock? Use special machine instructions: TestAndSet, Swap. do { acquire lock critical section release lock remainder section } while (TRUE);

62 Synchronization Solution: hardware support for mutual exclusion.
Complex machine instruction for hw synch: TestAndSet TestAndSet is a machine/assembly instruction. You must write the acquire-lock portion (entry section code) of your code in assembly. But here is a C code for easy understanding: --Definition of TestAndSet Instruction-- boolean TestAndSet (boolean *target) { boolean rv = *target; *target = TRUE; return rv: } //atomic (not interruptible)!!!!!!!!!!!!

63 Synchronization entry section exit section
Solution: hardware support for mutual exclusion. Complex machine instruction for hw synch: TestAndSet We use a shared Boolean variable lock, initialized to false. do { while ( TestAndSet (&lock ) ) ; //do nothing; busy wait // critical section lock = FALSE; //release lock // remainder section } while (TRUE); entry section exit section

64 Synchronization Solution: hardware support for mutual exclusion.
Complex machine instruction for hw synch: TestAndSet Can be suspended/interrupted b/w TestAndSet & CMP, but not during TestAndSet.

65 Synchronization Advertisement: Writing assembly in C is a piece of cake.

66 Synchronization Solution: hardware support for mutual exclusion.
Complex machine instruction for hw synch: Swap Swap is a machine/assembly instruction. You must write the acquire-lock portion (entry section code) of your code in assembly. But here is a C code for easy understanding: --Definition of Swap Instruction-- boolean Swap (boolean* a, boolean* b) { boolean temp = *a; *a = *b; *b = temp; } //atomic (not interruptible)!!!!!!!!!!!!

67 Synchronization entry sect exit sect
Solution: hardware support for mutual exclusion. Complex machine instruction for hw synch: Swap We use a shared Boolean variable lock, initialized to false. Each process also has a local Boolean variable key. do { key = TRUE; while (key == TRUE) Swap (&lock, &key ); // critical section lock = FALSE; // remainder section } while (TRUE); entry sect exit sect

68 Synchronization Solution: hardware support for mutual exclusion.
A comment on TestAndSwap & Swap. Although they both guarantee mutual exclusion, they may make one process (X) wait a lot: A process X may be waiting, but we can have the other process Y going into the critical region repeatedly. One toy/bad solution: keep the remainder section code so long that scheduler kicks Y out of the CPU before it reaches back to the entry section.

69 Synchronization How to avoid?
Solution: Semaphores (= shared integer variable). Idea: avoid busy waiting: waste of CPU cycles by waiting in a loop ‘till the lock is available, aka spinlock. Example1: while (flag[i] && turn == i); //from Peterson’s algo. Example2: while (TestAndSet (&lock )); //from TestAndSet algo. How to avoid? If a process P calls wait() on a semaphore with a value of zero, P is added to the semaphore’s queue and then blocked. The state of P is switched to the waiting state, and control is transferred to the CPU scheduler, which selects another process to execute (instead of busy waiting on P). When another process increments the semaphore by calling signal() and there are tasks on the queue, one is taken off of it and resumed. wait() = P() = down(). //modify semaphore s via these functions. signal() = V() = up(). //modify semaphore s via these functions. P() = proberen (German) = test. V() = verhogen (German) = increment.

70 Synchronization Solution: Semaphores. wait() = P() = down(). //modify semaphore s via these functions. signal() = V() = up(). //modify semaphore s via these functions. These functions can be implemented in kernel as system calls. Kernel makes sure that wait(s) & signal(s) are atomic. Less complicated entry & exit sections. Ensure: disable clock interrupts. Ensure: assume your CPU had atomic wait() and signal() instructions.

71 Synchronization Operations (kernel codes).
Solution: Semaphores. Operations (kernel codes). Busy-waiting  vs. Efficient  More formally, s->value--; s->list.add(this); etc. wait(): |s| is the # of processes that are blocked/sleeping/waiting on the semaphore s.

72 Synchronization Operations. Solution: Semaphores. wait(s):
if s positive s-- and return else s-- and block/wait (‘till somebody wakes you up; then return)

73 Synchronization Operations. Solution: Semaphores. signal(s): s++
if there’s 1+ process waiting (new s<=0) wake one of them up return

74 Synchronization Types. Binary semaphore Solution: Semaphores.
Integer value can range only between 0 and 1; can be simpler to implement; aka mutex locks. Provides mutual exclusion; can be used for the critical section problem. Counting semaphore Integer value can range over an unrestricted domain. Can be used for other synchronization problems; for example for resource allocation. Example: you have 10 instances of a resource. Init semaphore s to 10 in this case.

75 Synchronization Usage.
Solution: Semaphores. Usage. An integer variable s that can be shared by N processes/threads. s can be modified only by atomic system calls: wait() & signal(). s has a queue of waiting processes/threads that might be sleeping on it. Atomic: when process X is executing wait(), Y can execute wait() if X finished executing wait() or X is blocked in wait(). When X is executing signal(), Y can execute signal() if X finished. typedef struct { int value; struct process *list; } semaphore;

76 Synchronization Usage.
Solution: Semaphores. Usage. Binary semaphores (mutexes) can be used to solve critical section problems. A semaphore variable (lets say mutex) can be shared by N processes, and initialized to 1. Each process is structured as follows: do { wait (mutex); // Critical Section signal (mutex); // remainder section } while (TRUE);

77 Synchronization Process 0 do { wait (mutex); // Critical Section
Solution: Semaphores. Usage. Process 0 do { wait (mutex); // Critical Section signal (mutex); // remainder section } while (TRUE); Process 1 do { wait (mutex); // Critical Section signal (mutex); // remainder section } while (TRUE); Assume P0 in critical region, i.e. mutex=0. Kernel has linked list of processes waiting on Semaphore mutex; so kernel adds P1 to this list by calling block() (\in wait()) on it. When a wakeup() (\in signal()) is called, kernel picks a process from that linked list and resumes it (waiting  ready state). In busy-waiting solution, we were just looping around, but here we just put P1 to sleep mode and do something else. wait() {…} signal() {…} Kernel Semaphore mutex; //initialized to 1

78 Synchronization Usage.
Solution: Semaphores. Usage. Kernel puts processes/threads waiting on s in a FIFO queue. Why FIFO? To prevent starvation. IF you remove the last process added (stack) we can have starvation and never serve/wakeup the first process. Priority queues can be used though, to prioritize some threads, e.g., admin threads.

79 Synchronization P0 P1 … S1; …. … S2; ….
Solution: Semaphores. Usage other than critical section. Ensure S1 definitely executes before S2 (just a synch problem). P0 P1 S1; …. S2; ….

80 Synchronization P0 P1 … S1; …. … S2; …. Solution via semaphores:
Solution: Semaphores. Usage other than critical section. Ensure S1 definitely executes before S2 (just a synch problem). P0 P1 S1; …. S2; …. Solution via semaphores: Semaphore x = 0; //inited to 0 P0 P1 S1; signal (x); …. wait (x); S2; ….

81 Synchronization Usage other than critical section.
Solution: Semaphores. Usage other than critical section. Resource allocation (just another synch problem). We have N processes that want a resource that has 5 instances. Solution:

82 Synchronization Usage other than critical section.
Solution: Semaphores. Usage other than critical section. Resource allocation (just another synch problem). We’ve N processes that want a resource R that has 5 instances. Solution: Semaphore rs = 5; Every process that wants to use R will do wait(rs); If some instance is available, that means rs will be nonnegative  no blocking. If all 5 instances are used, that means rs will be negative  block ‘till rs nonneg. Every process that finishes with R will do signal(rs); A blocked processes will change state from waiting to ready.

83 Synchronization Producer Consumer Kernel
Solution: Semaphores. Usage other than critical section. Enforce consumer to sleep while there’s no item in the buffer (another synch problem). Producer Consumer do { // produce item .. put item into buffer signal (Full_Cells); } while (TRUE); do { wait (Full_Cells); //instead of busy-waiting, go to sleep mode and give CPU back to producer for faster production (efficiency!!). .. remove item from buffer } while (TRUE); Kernel Semaphore Full_Cells = 0; //initialized to 0

84 Synchronization Solution: Semaphores.

85 Synchronization Consumer can never cross the producer curve.
Solution: Semaphores. Consumer can never cross the producer curve. Difference b/w produced and consumed items can be <= BUFSIZE

86 Synchronization Problems with semapahores: Deadlock and Starvation.
Two or more processes are waiting indefinitely for an event that can be caused by only one of the waiting processes.

87 Synchronization Problems with semapahores: Deadlock and Starvation.
Two or more processes are waiting indefinitely for an event that can be caused by only one of the waiting processes.

88 Synchronization Problems with semapahores: Deadlock and Starvation.
Two or more processes are waiting indefinitely for an event that can be caused by only one of the waiting processes.

89 Synchronization Problems with semapahores: Deadlock and Starvation.
This code may sometimes (not all the time) cause a deadlock: P P1 wait(S); wait(Q); wait(Q); wait(S); . . signal(S); signal(Q); signal(Q); signal(S); When does the deadlock occur? How to solve? When? Context-switch right after PO executes wait(S); To solve, reverse the wait order in P0 (or P1); i.e. careful programming.

90 Synchronization Problems with semapahores: Deadlock and Starvation.
Indefinite blocking: a process may never be removed from the semaphore queue in which it is susupended; it’ll always be sleeping; no service. When does it occur? How to solve? Another problem: Low-priority process may cause high-priority process to wait. When? Kernel selects processes from semaphore.queue in LIFO (stack) manner. To solve, kernel employs FIFO queues.

91 Synchronization Classic Synchronization Problems to be solved with Semaphores. Bounded-buffer problem. Readers-Writers problem. Dining philosophers problem. Rendezvous problem. Barrier problem.

92 Synchronization prod cons buffer full = 4 empty = 6
Classic Synchronization Problems to be solved with Semaphores. Bounded-buffer problem (aka Producer-Consumer problem). Producer should not produce any item if the buffer is full: Semaphore full = 0; //inited Consumer should not consume any item if the buffer is empty: Semaphore empty = N; Producer and consumer should access the buffer in a mutually exc manner: mutex = 1; Types of 3 semaphores above? prod cons buffer mutually exc manner: one process at a time. full & empty: counting semaphores; mutex: binary semaphore. full = 4 empty = 6

93 Synchronization Classic Synchronization Problems to be solved with Semaphores. Bounded-buffer problem. Producer should not produce any item if the buffer is full: Semaphore full = 0; //inited Consumer should not consume any item if the buffer is empty: Semaphore empty = N; Producer and consumer should access the buffer in a mutually exc manner: mutex = 1; Think about the code of this?

94 Synchronization Classic Synchronization Problems to be solved with Semaphores. Bounded-buffer problem. Producer should not produce any item if the buffer is full: Semaphore full = 0; //inited Consumer should not consume any item if the buffer is empty: Semaphore empty = N; Producer and consumer should access the buffer in a mutually exc manner: mutex = 1;

95 Synchronization Classic Synchronization Problems to be solved with Semaphores. Readers-Writers problem. A data set is shared among a number of concurrent processes. Readers: only read the data set; they do not perform any updates. Writers: can both read and write. Problem: allow multiple readers to read at the same time. Only one single writer can access the shared data at the same time (no reader/writer when writer is active). Data: can be a file, shared global var, shared linked list, var in shared memory, grades in oibs.metu, etc.

96 Synchronization Classic Synchronization Problems to be solved with Semaphores. Readers-Writers problem. A data set is shared among a number of concurrent processes. Readers: only read the data set; they do not perform any updates. Writers: can both read and write. Problem: allow multiple readers to read at the same time. Only one single writer can access the shared data at the same time (no reader/writer when writer is active). Integer readcount initialized to 0. Number of readers reading the data at the moment. Semaphore mutex initialized to 1. Protects the readcount variable (multiple readers may try to modify it). Semaphore wrt initialized to 1. Protects the shared data (either writer or reader(s) should access data at a time).

97 Synchronization Classic Synchronization Problems to be solved with Semaphores. Readers-Writers problem. A data set is shared among a number of concurrent processes. Readers: only read the data set; they do not perform any updates. Writers: can both read and write. Problem: allow multiple readers to read at the same time. Only one single writer can access the shared data at the same time (no reader/writer when writer is active). Think about the code of this? Reader and writer processes running in (pseudo) parallel. Hint: first and last reader should do something special. Special operation: acquire/release lock for the shared data.

98 Synchronization Classic Synchronization Problems to be solved with Semaphores. Readers-Writers problem. A data set is shared among a number of concurrent processes. Readers: only read the data set; they do not perform any updates. Writers: can both read and write. //acquire lock to shared data. //release lock of shared data.

99 Synchronization Classic Synchronization Problems to be solved with Semaphores. Readers-Writers problem. Case1: First reader acquired the lock, reading, what happens if writer arrives? Case2: First reader acquired the lock, reading, what happens if reader2 arrvs? Case3: Writer acquired the lock, writing, what happens if reader1 arrives? Case4: Writer acquired the lock, writing, what happens if reader2 arrives? wait(wrt): ensure that I’m the only writer (no reader or other writer) updating shared data. wait(mutex): ensure that I’m the only reader updating readcount.

100 Synchronization Classic Synchronization Problems to be solved with Semaphores. Dining philosophers problem. A philosopher (process) needs 2 forks (resources) to eat. While a philosopher is holding a fork, it’s neighbor cannot have it.

101 Synchronization Classic Synchronization Problems to be solved with Semaphores. Dining philosophers problem. A philosopher (process) needs 2 forks (resources) to eat. While a philosopher is holding a fork, it’s neighbor cannot have it.

102 Synchronization Classic Synchronization Problems to be solved with Semaphores. Dining philosophers problem. A philosopher (process) needs 2 forks (resources) to eat. While a philosopher is holding a fork, it’s neighbor cannot have it.

103 Synchronization Classic Synchronization Problems to be solved with Semaphores. Dining philosophers problem. A philosopher (process) needs 2 forks (resources) to eat. While a philosopher is holding a fork, it’s neighbor cannot have it.

104 Synchronization Classic Synchronization Problems to be solved with Semaphores. Dining philosophers problem. A philosopher (process) needs 2 forks (resources) to eat. While a philosopher is holding a fork, it’s neighbors cannot have it. Not gay , just going for a fork.

105 Synchronization Classic Synchronization Problems to be solved with Semaphores. Dining philosophers problem. A philosopher (process) needs 2 forks (resources) to eat. While a philosopher is holding a fork, it’s neighbors cannot have it. Philosopher in 2 states: eating (needs forks) and thinking (not need forks). We want parallelism, e.g., 4 or 5 (not 1 or 3) can be eating while 2 is eating. We don’t want deadlock: waiting for each other indefinitely. We don’t want starvation: no philosopher waits forever (starves to death).

106 Synchronization Classic Synchronization Problems to be solved with Semaphores. Dining philosophers problem. A philosopher (process) needs 2 forks (resources) to eat. While a philosopher is holding a fork, it’s neighbors cannot have it. A solution that provides concurrency but not deadlock prevention: Semaphore forks[5]; //inited to 1 (assume 5 philosophers on table). do { wait( forks[i] ); wait( forks[ (i + 1) % 5] ); // eat signal( forks[i] ); signal( forks[ (i + 1) % 5] ); // think } while(TRUE);

107 Synchronization Classic Synchronization Problems to be solved with Semaphores. Dining philosophers problem. A philosopher (process) needs 2 forks (resources) to eat. While a philosopher is holding a fork, it’s neighbors cannot have it. A solution that provides concurrency but not deadlock prevention: How is deadlock possible?

108 Synchronization Classic Synchronization Problems to be solved with Semaphores. Dining philosophers problem. A philosopher (process) needs 2 forks (resources) to eat. While a philosopher is holding a fork, it’s neighbors cannot have it. A solution that provides concurrency but not deadlock prevention: How is deadlock possible? Deadlock in a circular fashion: 4 gets the left fork, context switch (cs), 3 gets the left fork, cs, .. , 0 gets the left fork, cs, 4 now wants the right fork which is held by 0 forever. Unlucky sequence of cs’s not likely but possible. A perfect solution w/o deadlock danger is possible with again semaphores. Solution #1: put the left back if you cannot grab right. Solution #2: grab both forks at once (atomic).

109 Synchronization Classic Synchronization Problems to be solved with Semaphores. Rendezvous problem. 2 threads rendezvous at a point of execution, and neither is allowed to proceed until both arrived. Guarantee that a1 happens before b2 and b1 happens before a2. Solution?

110 Synchronization Classic Synchronization Problems to be solved with Semaphores. Rendezvous problem. 2 threads rendezvous at a point of execution, and neither is allowed to proceed until both arrived. Guarantee that a1 happens before b2 and b1 happens before a2. Solution: initially aArrived = bArrived = 0; //arrived at the rendezvous (line 2).

111 Synchronization Classic Synchronization Problems to be solved with Semaphores. Rendezvous problem. 2 threads rendezvous at a point of execution, and neither is allowed to proceed until both arrived. Guarantee that a1 happens before b2 and b1 happens before a2. Solution: initially aArrived = bArrived = 0; //arrived at the rendezvous (line 2). Less efficient: might have to switch b/w A and B 1 time more than necessary. A arrives first.

112 Synchronization Classic Synchronization Problems to be solved with Semaphores. Rendezvous problem. 2 threads rendezvous at a point of execution, and neither is allowed to proceed until both arrived. Guarantee that a1 happens before b2 and b1 happens before a2. Solution: initially aArrived = bArrived = 0; //arrived at the rendezvous (line 2). Any problem?

113 Synchronization Classic Synchronization Problems to be solved with Semaphores. Rendezvous problem. 2 threads rendezvous at a point of execution, and neither is allowed to proceed until both arrived. Guarantee that a1 happens before b2 and b1 happens before a2. Solution: initially aArrived = bArrived = 0; //arrived at the rendezvous (line 2). Any problem? Yes, deadlock!

114 Synchronization Classic Synchronization Problems to be solved with Semaphores. Barrier problem. Generalization of Rendezvous problem to more than 2 threads. That is, no thread executes critical point until after all threads have executed rendezvous. That is, when the first n-1 threads arrive, they should block until the nth thread arrives. Solution?

115 Synchronization Classic Synchronization Problems to be solved with Semaphores. Barrier problem. Generalization of Rendezvous problem to more than 2 threads. That is, no thread executes critical point until after all threads have executed rendezvous. That is, when the first n-1 threads arrive, they should block until the nth thread arrives. Solution: semaphore mutex = 1, barrier = 0; int n = 5, count = 0;

116 Synchronization Classic Synchronization Problems to be solved with Semaphores. Barrier problem. Generalization of Rendezvous problem to more than 2 threads. That is, when the first n-1 threads arrive, they should block until the nth thread arrives. Solution: semaphore mutex = 1, barrier = 0; int n = 5, count = 0; First n-1 threads wait when they get to the barrier. nth thread unlocks the barrier.

117 Synchronization Classic Synchronization Problems to be solved with Semaphores. Barrier problem. Generalization of Rendezvous problem to more than 2 threads. That is, when the first n-1 threads arrive, they should block until the nth thread arrives. Solution: semaphore mutex = 1, barrier = 0; int n = 5, count = 0; Problem?

118 Synchronization Classic Synchronization Problems to be solved with Semaphores. Barrier problem. Generalization of Rendezvous problem to more than 2 threads. That is, when the first n-1 threads arrive, they should block until the nth thread arrives. Solution: semaphore mutex = 1, barrier = 0; int n = 5, count = 0; Problem: deadlock! nth thread signals 1 of the waiting threads. No one signals again.

119 Synchronization Classic Synchronization Problems to be solved with Semaphores. Barrier problem. Generalization of Rendezvous problem to more than 2 threads. That is, when the first n-1 threads arrive, they should block until the nth thread arrives. Solution: semaphore mutex = 1, barrier = 0; int n = 5, count = 0; Correct solution! No deadlock.

120 Synchronization Classic Synchronization Problems to be solved with Semaphores. Barrier problem. Generalization of Rendezvous problem to more than 2 threads. That is, when the first n-1 threads arrive, they should block until the nth thread arrives. Solution: semaphore mutex = 1, barrier = 0; int n = 5, count = 0; Problem: deadlock! 1st thread blocks. Since mutex is locked no one can do count++.

121 Synchronization Classic Synchronization Problems to be solved with Semaphores. Barrier problem. Generalization of Rendezvous problem to more than 2 threads. That is, when the first n-1 threads arrive, they should block until the nth thread arrives. Solution: semaphore mutex = 1, barrier = 0; int n = 5, count = 0; Common deadlock source: blocking on a semaphore while holding mutex.

122 Synchronization Problems with semaphores. Careless programmer may do
signal(mutex); .. wait(mutex); //2+ threads in critical region (unprotected). wait(mutex); .. wait(mutex); //deadlock (indefinite waiting). Forgetting corresponding wait(mutex) or signal(mutex); //unprotect & deadlck Need something else, something better, something easier to use: Monitors.

123 Synchronization Solution: Monitors.
Idea: get help not from the OS but from the programming language. High-level abstraction for process/thread synchronization. C does not provide monitors (use semaphores) but Java does. Compiler ensures that the critical regions of your code are protected. You just identify the critical section of the code, put them into a monitor, and compiler puts the protection code. Monitor implementation using semaphores. Compiler writer/language developer has to worry about this stuff, not the casual application programmer.

124 Synchronization Solution: Monitors.
Monitor is a construct in the language, like class construct: monitor construct guarantees that only one process may be active within the monitor at a time. monitor monitor-name { // shared variable declarations procedure P1 (..) { .. } .. procedure Pn (..) { .. } Initialization code (..) { .. } }

125 Synchronization Solution: Monitors.
monitor construct guarantees that only one process may be active within the monitor at a time. This means that, if a process is running inside the monitor (= running a procedure, say P1()), then no other process can be active inside the monitor (= can run P1() or any other procedure of the monitor) at the same time. Compiler is putting some locks/semaphores to the beginning/ending of these critical regions (procedures, shared variables, etc.). So it is not the programmer’s job anymore to insert these locks/semaphores.

126 Synchronization Solution: Monitors. Schematic view of a monitor.
All other processes that want to be active in the monitor (execute a monitor procedure) must wait in the queue ‘till current active P leaves.

127 Synchronization Solution: Monitors. Schematic view of a monitor.
This monitor solution solves the critical section (mutual exc.) problem. But not the other synchronization problems such as produc-consume, dining philosophs.

128 Synchronization Solution: Monitors.
Condition variables to solve all the synchronization problems. In previous model, no way to enforce a process/thread to wait ‘till a condition happens. Now we can  Using conition variables. condition x, y; Two operations on a condition variable: x.wait (): a process that invokes the operation is suspended. Execute wait() operation on the condition variable x. x.signal(): resumes one of processes (if any) that invoked x.wait(). Usually the first one that is blocked is waken up (FIFO).

129 Synchronization Solution: Monitors. condition x, y;
wait(Semaphore s); //you may or may not block depending on s.value x.wait () //you (= process) definitely block. No integer is attached to x (unlike s.value).

130 Synchronization Solution: Monitors.
Schematic view of a monitor w/ condition variables. If currently active process wants to wait (e.g., empty buffer), it calls x.wait() and added to the queue of x, and it is no longer active.

131 Synchronization Solution: Monitors.
Schematic view of a monitor w/ condition variables. New active process in the monitor (fetched from the entry queue), does x.signal() from a different/same procedure. Prev. process resumes from where it got blocked.

132 Synchronization Solution: Monitors.
Schematic view of a monitor w/ condition variables. Now we may have 2 processes active: caller of x.signal & waken-up. Solution: put x.signal() as the last statement in the procedure.

133 Synchronization Solution: Monitors.
Schematic view of a monitor w/ condition variables. Now we may have 2 processes active: caller of x.signal & waken-up. Solution: call x.wait() right after x.signal() to block the caller.

134 Synchronization Solution: Monitors.
An example: We have 5 instances of a resource and N processes. Only 5 processes can use the resource simultaneously. Process code Monitor code

135 Synchronization Solution: Monitors. An example: Dining philosophers.
monitor DP { enum { THINKING, //not holding/wanting resoursces HUNGRY, //not holding but wanting EATING} //has the resources state[5]; condition cond[5]; //no need for entry/exit code to pickup() ‘cos its in monitor void pickup (int i) { state[i] = HUNGRY; test(i); if (state[i] != EATING) cond[i].wait; } void putdown (int i) { state[i] = THINKING; // test left and right neighbors test((i + 4) % 5) test((i + 1) % 5); void test (int i) { if ( (state[(i + 4) % 5] != EATING) && (state[(i + 1) % 5] != EATING) && (state[i] == HUNGRY)) { state[i] = EATING ; cond[i].signal (); } //initially all thinking initialization_code() { for (int i = 0; i < 5; i++) state[i] = THINKING; } /* end of monitor */

136 Synchronization Solution: Monitors.
One philosopher/process doing this in an endless loop: .. DP DiningPhilosophers; while (1) { //THINK.. DiningPhilosophters.pickup (i); //EAT (use resources) DiningPhilosophers.putdown (i); } Philosopher i:

137 Synchronization #define LEFT ? THINKING? HUNGRY? EATING?
Solution: Monitors. First things first; what are the ID’s to access neighbors? #define LEFT ? THINKING? HUNGRY? EATING? #define RIGHT ? state[LEFT] = ? state[i] = ? state[RIGHT] = ? Process ?? Process i Process ?? .. ..

138 Synchronization #define LEFT (i+4)%5 THINKING? HUNGRY? EATING?
Solution: Monitors. General idea. #define LEFT (i+4)%5 THINKING? HUNGRY? EATING? #define RIGHT (i+1)%5 state[LEFT] = ? state[i] = ? state[RIGHT] = ? Process (i+4) % 5 Process i Process (i+1) % 5 Test(i) Test((i+4) %5) Test((i+1) %5)

139 Processes or Threads that want to use the resource
Synchronization Solution: Monitors. An example: Allocate a resource to one of the several processes. Priority-based: The process that will use the resource for the shortest amount of time (known) will get the resource first if there are other processes that want the resource. Processes or Threads that want to use the resource .. Resource

140 Synchronization 10 20 45 70 Solution: Monitors.
An example: Allocate a resource to one of the several processes. Assume we have condition variable implementation that can enqueue sleeping/waiting processes w.r.t. a priority specified as a parameter to wait() call. condition x; x.wait (priority); Queue of sleeping processes waiting on condition x: x 10 20 45 70 priority could be the time-duration to use the resource.

141 Synchronization Solution: Monitors.
An example: Allocate a resource to one of the several processes. monitor ResourceAllocator { boolean busy; //true if resource is currently in use/allocated condition x; //sleep the process that cannot acquire the resource void acquire(int time) { if (busy) x.wait(time); busy = TRUE; } void release() { busy = FALSE; x.signal(); //wakeup the P at the head of the waiting qu initialization_code() { busy = FALSE; } }

142 Synchronization .. Solution: Monitors.
An example: Allocate a resource to one of the several processes. Process/Thread 1 Process/Thread 2 Process/Thread N ResourceAllocator RA; RA.acquire(10); ..use resource.. RA.release(); ResourceAllocator RA; RA.acquire(30); ..use resource.. RA.release(); ResourceAllocator RA; RA.acquire(25); ..use resource.. RA.release(); .. Each process should use resource between acquire() and release() calls.


Download ppt "CENG 334 – Operating Systems 03- Threads & Synchronization"

Similar presentations


Ads by Google