330 likes | 542 Views
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. Summary. Motivation & Objective
E N D
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
Summary • Motivation & Objective • Synchronization Problems • Nachos Synchronization Routines • Semaphore • Lock • Condition • Requirement • Submission
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
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 Buffer Producer Consumer
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
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
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
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 introducing 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
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
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(); }
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
Monitor example • Following pseudo code demonstrate a channel which can only store one integer value at a time function receive() { var int received while not full do wait(snd) received := contents full := false notify(rcv) return received } }//End of monitor 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) } 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
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
Implementation • Implements producer-consumer problem with semaphores and locks (built by Nachos) • Implements dining philosopher problem with a monitor-style class (built by you)
Implementation (cont.) • Make Nachos run producer-consumer problem with flag -PC, and run dining philosopher problem with flag -DP • Just like -K (ThreadSelfTest) and -S (previous project) flags
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“, ...); } }
Producer-Consumer problem (cont.) Produced item 0, Total 1 item(s) Produced item 1, Total 2 item(s) Consumed item 0, Total 1 item(s) Consumed item 1, Total 0 item(s) Produced item 2, Total 1 item(s) Produced item 3, Total 2 item(s) Produced item 4, Total 3 item(s) Consumed item 2, Total 2 item(s) Produced item 5, Total 3 item(s) Consumed item 3, Total 2 item(s) Consumed item 4, Total 1 item(s) Produced item 6, Total 2 item(s) Consumed item 5, Total 1 item(s) Produced item 7, Total 2 item(s) Produced item 8, Total 3 item(s) Produced item 9, Total 4 item(s) Consumed item 6, Total 3 item(s) ... • Sample output:
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
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); } }
Dining Philosopher problem (cont.) 0: Philosopher 0 is eating 0: Philosopher 0 is thinking 1: Philosopher 0 is eating 0: Philosopher 3 is eating 0: Philosopher 3 is thinking 0: Philosopher 2 is eating 1: Philosopher 0 is thinking 0: Philosopher 2 is thinking 0: Philosopher 1 is eating 0: Philosopher 1 is thinking 1: Philosopher 1 is eating 1: Philosopher 1 is thinking 0: Philosopher 4 is eating 0: Philosopher 4 is thinking 2: Philosopher 0 is eating 1: Philosopher 3 is eating ... • Sample output:
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…
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.
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 } }
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
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)
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 • E.g. in second 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 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
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.
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: • A report in .pdf or .doc • threads.cc, threads.h, and main.cc • Send your files • Tar your files to an archieve named: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 auto-reply system will not work tar zcvf os_hw3_bXXXXXXXX_bOOOOOOOO.tar.gz report.doc other files...
Submission (cont.) • Deadline: 11/10 24:00 • For the delayed submission, deduct 5 points for each day • DO NOT COPY!!Protect your code well!!