Nachos Project 3 Lecturer: Hao-Hua Chu TA: Chun-Po Wang (Artoo) Date: 2008/10/25 Project 3 • Practice thread synchronization – Producer-consumer problem – Dining philosophers problem – Implement these problems with Nachos thread and synchronization routines 2 Summary • Motivation & Objective • Synchronization Problems • Nachos Synchronization Routines – Semaphore – Lock – Condition • Requirement • Submission 3 Motivation & Objective • In previous project, you learnt to run multiple threads. – Now we should practice to make them work together by implementing some classic synchronization problems • Nachos has already implemented some class and routines to help you solve these problems 4 Synchronization Problems • Producer-consumer problem – – – – – A fixed-size buffer Producer generates data and put it into buffer Consumer retrieves data from buffer They work simultaneously The objective is to make sure that • producer won’t put data into buffer when it’s full • consumer won’t remove data from en empty buffer • the state of buffer is consistent after each action Producer Buffer Consumer 5 Producer-consumer problem • When it comes to Nachos … – A buffer may be a global variable, e.g. a global array – The consumer and the producer would be threads • Where the problem lies … – Threads may be yielded at any point – When both threads access the same data, we must make sure that a thread won’t access it when another is working on it. – Or, the data may be corrupted 6 Synchronization Problems (cont.) • Dining philosophers problem – 5 philosophers, with 5 chopsticks – A philosopher can either think or eat – When she/he want to eat, she/he must take both chopsticks on her/his left and right – If all philosophers hold one chopstick, we have a deadlock (philosophers are strange, aren’t they?) http://en.wikipedia.org/wiki/Image:Dining_philosophers.png 7 Dining philosophers problem • Now we deal with 5 threads, apparently • Figure out a method to prevent deadlock – An easy solution is to make sure that a philosopher need to pick up both chopsticks at the same time 8 Nachos synchronization classes • code/thread/synch.h and synch.cc • Semaphore – A counter for a set of available resources – Nachos promised that the actions on semaphores will not be interrupted • Lock – A lock can be either BUSY or FREE – Only the thread that acquired the lock can release it – Lock is implemented by semaphores • Condition – A condition variable is used on monitors, where many threads wait on this condition – We discuss condition variables when introducing9 monitors later Semaphore class • int value; // value >= 0 • P(): waits until value > 0, then decreases it • V(): increases the value, and wakes up a thread waiting in P() • How does OS make these operations atomic? – Disable interrupts (this method only works on uniprocessor machines), or – Use special instructions (eg. test-and-set) – Nachos uses the former 10 Lock class • A Lock is implemented by a Semaphore: – value 0 means busy, value 1 means free • Acquire(): wait until the lock is free, then set it to busy (by calling Semaphore::P()) • Release(): release the lock and wake up a thread waiting on this lock (by calling Semaphore::V()) – However, only the thread which acquired the lock can release it – This is different to a semaphore 11 An example • See class SynchConsoleOutput (code/userprog/synchconsole.cc) void SynchConsoleOutput::PutChar(char ch) { lock->Acquire(); consoleOutput->PutChar(ch); waitFor->P(); lock->Release(); } void SynchConsoleOutput::CallBack() { waitFor->V(); } 12 Monitor • “A monitor is an approach to synchronize two or more computer tasks that use a shared resource” • Why use it? Why not just use semaphores? – Programmers are prone to errors • A monitor consists of – Procedures for manipulating shared resources – A mutual exclusion lock: only one thread can operate in a monitor at any time – Conditions (optional): sometimes a thread operating in a monitor must wait for some condition to be true before it proceeds 13 Monitor example • Following pseudo code demonstrate a channel which can only store one integer value at a time monitor channel { int contents boolean full := false condition snd condition rcv function send(int message) { while full do wait(rcv) contents := message full := true notify(snd) } function receive() { var int received while not full do wait(snd) received := contents full := false notify(rcv) return received } }//End of monitor 14 Source: http://en.wikipedia.org/wiki/Monitor_(synchronization) Condition class • Nachos DOES NOT provide a Monitor class, but it does provide a Condition class, which can be used to build a “monitor-style” C++ class – E.g. class SynchList (code/thread/synchlist.cc), this is a List which can be accessed by multiple threads without any synchronization problems 15 Condition class (cont.) • Wait(lock): a thread in a monitor waits for this condition. – The lock is supplied by the monitor which uses this condition, because when a thread waits, it should release the lock for other threads to operate in the monitor • Signal(): the condition is met, and a monitor wakes up a thread waiting on this condition. • Broadcast(): just like Signal(), but now the monitor wakes up all threads waiting on the condition 16 Implementation • Implements producer-consumer problem with semaphores and locks (built by Nachos) • Implements dining philosopher problem with a monitor-style class (built by you) 17 Implementation (cont.) • Make Nachos run producerconsumer problem with flag -PC, and run dining philosopher problem with flag -DP – Just like -K (ThreadSelfTest) and -S (previous project) flags 18 Producer-Consumer problem • Produce and consume 30 items: 0~29 • The buffer size is 5 • Print the item number you produced or consumed, and the current total number of items in the shared buffer // Thread body for producer void Producer(int arg) { int i; for(i=0;i<30;i++){ // Produce item i here (maybe stores it in a global array) printf("Produced item %d, Total %d item(s)\n“, ...); } } // Thread body for consumer void Consumer(int arg) { int i; for(i=0;i<30;i++){ // Consume item i here (maybe retrieve it from a global array) printf(“Consumed item %d, Total %d item(s)\n“, ...); 19 } } Producer-Consumer problem (cont.) • Sample output: Produced Produced Consumed Consumed Produced Produced Produced Consumed Produced Consumed Consumed Produced Consumed Produced Produced Produced Consumed ... item item item item item item item item item item item item item item item item item 0, 1, 0, 1, 2, 3, 4, 2, 5, 3, 4, 6, 5, 7, 8, 9, 6, Total Total Total Total Total Total Total Total Total Total Total Total Total Total Total Total Total 1 2 1 0 1 2 3 2 3 2 1 2 1 2 3 4 3 item(s) item(s) item(s) item(s) item(s) item(s) item(s) item(s) item(s) item(s) item(s) item(s) item(s) item(s) item(s) item(s) 20 item(s) Dining Philosopher problem • Following skeleton is an example, you can design by yourself • A monitor-style class DiningTable: • We have 5 philosophers (0~4), which means 5 threads • Each philosopher starts at thinking, then eating, then thinking … for 10 times • Print what a philosopher is doing when she/he starts to do that 21 Dining Philosopher problem (cont.) class DiningTable { public: void pickup (int it, int id) { // Philosopher “id” wants to eat for the “it”-th times printf(“%d: Philosopher %d is eating\n”, it, id); } void putdown (int it, int id) { // Philosopher “id” goes back to think for the “it”-th times printf(“%d: Philosopher %d is thinking\n”, it, id); } } DiningTable dining_table; // Thread body for each philosopher void philosopher (int id) { for(int i=0; i<10; i++) { dining_table.pickup(i, id); dining_table.putdown(i, id); } } 22 Dining Philosopher problem (cont.) • Sample output: 0: Philosopher 0: Philosopher 1: Philosopher 0: Philosopher 0: Philosopher 0: Philosopher 1: Philosopher 0: Philosopher 0: Philosopher 0: Philosopher 1: Philosopher 1: Philosopher 0: Philosopher 0: Philosopher 2: Philosopher 1: Philosopher ... 0 0 0 3 3 2 0 2 1 1 1 1 4 4 0 3 is is is is is is is is is is is is is is is is eating thinking eating eating thinking eating thinking thinking eating thinking eating thinking eating thinking eating eating 23 Please do me a favor • We all know that in a real system synchronization problems arise because threads can be interrupted at any point • However, currently we are working in Nachos kernel, not in user programs, and we don’t call OneTick() so no interrupts would occur in our thread body implementations. • Things will become too easy… 24 Please do me a favor (cont.) • So, let’s make it HARD! • Please put following code into your thread bodies, and every functions you built which may be called in thread bodies. • This code will call kernel->currentThread->Yield() with some probability, effectively interrupts your code at any point. 25 Please do me a favor (cont.) #define PY { if(rand()%5==0) { kernel->currentThread->Yield(); } } ... class DiningTable { public: void pickup (int it, int id) { PY printf(“%d: Philosopher %d is eating\n”, it, id); } void putdown (int it, int id) { PY printf(“%d: Philosopher %d is thinking\n”, it, id); } } ... void philosopher (int id) { PY for(int i=0; i<10; i++) { PY dining_table.pickup(i, id); PY dining_table.putdown(i, id); PY } } 26 Please do me a favor (cont.) • Please add “{ }” to all if-else and for,while loops, or this macro would mess up your program. • You can change random seed to see different results by using flag “-rs” – ./nachos -rs 100 -PC – Please test with different random seed to make sure that your implementations are correct 27 Some notes • Just like Thread, Semaphore, Lock, and Condition all have a name argument in constructor: please DO NOT provide a local string to them • You should make sure that main thread (the only thread which builds other threads) “waits” until other threads finish – How to do? This is also a synchronization problem (an easy one) 28 Requirement • Implement 2 synchronization problems – Make sure that no deadlock would occur – The output should be reasonable • E.g. in first problem, following output is wrong Produced item 0, Total 1 item(s) Produced item 1, Total 1 item(s) # Total should be 2 Consumed item 2, Total 1 item(s) # Item 2 is not generated yet • E.g. in second problem, following output is wrong 0: Philosopher 0 is eating 0: Philosopher 1 is eating # Ph. 1 cannot eat because Ph. 0 is eating and holding the chopstick Ph. 1 needs 29 Requirement • Write a 2-page report – Don’t just paste your code, I’ll read it myself – Explain why your implementations would generate correct output, and why there is no deadlock • If your project submission can’t compile and execute on Linux in Workstation Room 217, we will consider it as fail. – Please contact me to apply a workstation account if you need it. 30 Submission • Two people in one group (Write down the name and student ID of all members in the report) The file you need to send: • 1. 2. • A report in .pdf or .doc threads.cc, threads.h, and main.cc Send your files – Tar your files to an archieve named:other files... tar zcvf os_hw3_bXXXXXXXX_bOOOOOOOO.tar.gz report.doc – – os_hw3_bXXXXXXXX_bOOOOOOOO.tar.gz E-mail to artoo@csie.ntu.edu.tw with following title: [os_hw3] bXXXXXXXX_bOOOOOOOO Please follow the format carefully or our autoreply system will not work 31 Submission (cont.) • Deadline: 11/10 24:00 – For the delayed submission, deduct 5 points for each day • DO NOT COPY!! Protect your code well!! 32