This solution refers to the presidential elections problem on slide 25 of the midterm review/lecture 9. (a) Without synchronization, we have a race condition for the counters as multiple threads read and update them. In this case, it can lead to loss of votes. (b) Well, we need mutual exclusion on the access of the counters. We have options in terms of the granularity of the critical region. One implementation could use a single mutex where any thread that is incrementing a counter would use before entering the critical region. The drawback is it serializes access to the critical region even between votes to different candidate where there is no race condition. Another is to have a lock for each candidate independently -- so only threads that need to increment the count for a given candidate would acquire the lock. (c) The idea has advantages because most updates become private and do not need a lock to be acquired. We still need a lock when the local votes are reflected back to the global vote count, but this can happen significantly less frequently. The drawback is that the global count value is not as fresh as before; however, by balancing how frequently we update the global counter a good tradeoff between freshness and overhead may be found. ---- Problem on slide 26 (A) simulating dominoes. Each thread will have to wait for the domino before to fall, before telling the domino after it that it can fall. Start by initializing an array of 20 semaphores to 0, other than sem[0] which we will initialize to 1 (indicating that the first domino may fall). For domino i, the code is: domino(int i) { sem[i].down(); fall(); sem[i+1].up(); } This is a slight variation on the solution I did in class. Rather than having a special case for domino 0, we just initialize its semaphore to 1. Thread 20 will signal sem[21] which is not used, but this is ok. If you want you can check if i==20 and not signal. (B) //intialize semaphore restaurant to N where N is the capacity of the restaurant restaurant() { restaurant.down(); eat_drink_and_be_merry(); restaurant.up(); } This is a classical semaphore pattern. (C) //initialize two semaphores sem[0] and sem[1]. Make one of them 1 to indicate frisbee and the other 0. play(int i) { //i is 0 or 1 indicating the player sem[i].down(); throw_frisbee(); sem[1-i].up(); } If it is easier for you, write the code for the two threads separately with hard coded value for i. Note the similarity between the solutions for all three cases-- there are subtle differences in the initialization of the semaphores and which semaphores are signalled. --- Problem on page 27 of slides: MLF with 3 queues. Top two queues are RR with slices 5ms and 10ms respectively. Final queue is FCFS. We have jobs J1, J2, ... J5 of length 9, 16, 4, 20 and 7ms that arrive at times 0, 3, 6, 8 and 10 ms respectively. What is the average turnaround time. Explanation is below but timeline is: J1 (0-5), J2 (5-10), J3 (10-14), J4 (14-19), J5 (19-24), J1 (24-28), J2 (28-38), J4 (38-48), J5 (48-50), J2 (50-51), J4 (51-56). Time 0, first job arrives. Runs for 5ms, then gets demoted to the second queue, with 4ms remaining time. Meanwhile, job 2 arrives at time 3 and gets scheduled at time 5 to run for 5 ms. It gets demoted to second queue, which now has J1->J2, with remaining time of 11ms. By time 10, all the jobs have arrived, and we have in the top queue J3->J4->J5 (in the order they arrived). We schedule J3, which runs for 4 ms then finishes. At time 14, we schedule J4 which runs for 5 ms then gets demoted at time 19 to the second queue which now has J1->J2->J4. J4 has 15 ms left. At time 19, we run J5 (only job in queue 1) for 5 ms. J5 gets demoted to Queue 2 with 2 ms left. At this point, the top queue is empty and the second queue has J1->J2->J4->J5 with 4, 11, 15, and 2 ms left respectively. We run J1 for 4 ms, and it finishes. We run J2 for 10 ms, then it gets demoted to the bottom queue with 1ms left. We run J4 for 10 ms, then it gets domoted to the bottom queue with 5 ms left. We run J5 for 2 ms, and it finishes. Finally, J2 runs for 1ms and finishes, and J4 for 5 ms and finishes out of the bottom queue. Compute normalized turnaround time for each process as Tfinish-Tarrival/size J1---> 28-0/9 J2---> 51-3/16 etc... --------------------- Problem on slide 28 Sem Frisbees[i]; //initialized to 1 for each player who has a frisbee -- 0 otherwise Sem Available[i]; //initialized to 1 for each player who does not have a frisbee -- 0 otherwise Player(i) { while(1) { Available[(i+1) % N].Down(); //wait until receiver does not have a frisbee Frisbee[i].Down(); //wait until we have a frisbee Frisbee[(i+1)%N].Up(); //frisbee now with next guy Available[i].Up(); //we are now free } } (b) Yes, we have deadlock. Available semaphores will all be zero, and all players will be stuck on the first line of the code. --------------- Problem on slide 29: (a) Why ticket number needed in Bakery algorithm: Recall that bakery algorithm is a software critical region algorithm for N threads. It needs to enforce (1) mutual exclusion (safety); (2) Progress; and (3) bounded wait. Mutual exclusion is enforced by letting the thread with the lowest ticket (considering tie break) in. Progress is ensured naturally if the thread eventually leaves the critical region. Bounded wait is ensured by making sure that your ticket number bounds the wait to at most N threads. A thread that gets in before you this time, will get ticket number higher than you if you are still waiting when it comes again. So ticket numbers help with mutual exclusion and bounded wait. (b) using number of times in CR instead of ticket number gives mutual exclusion (with tie break) but does not do great with bounded wait. If a thread has been in many times before, competes with newer threads it may wait unbounded time while their use of critical region catches up.