synchronization txt synchronization txt
play

synchronization.txt synchronization.txt Feb 2 2009 1:10 Page 1 - PowerPoint PPT Presentation

synchronization.txt synchronization.txt Feb 2 2009 1:10 Page 1 Feb 2 2009 1:10 Page 2 A summary of the synchronization lecture (and additions)


  1. synchronization.txt synchronization.txt Feb 2 2009 1:10 Page 1����������� Feb 2 2009 1:10 Page 2����������� A summary of the synchronization lecture (and additions) −−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−− thread A and B 1 while (busy) main ; int i = 0; 2 busy = true; start thread A 3 copy i to eax start thread B 4 increment eax wait for threads to finish 5 copy eax to i printf("value of i: %d\n", i); 6 busy = false; thread A Let us still assume a interrupt and thread switch at same code as ++i before, now between A4 and A5: thread B A1 A2 A3 A4 B1 B1 B1 B1 B1 B1 ... ++i busy f f t t t t t t t t t i 0 0 0 0 0 0 0 0 0 0 0 The instructions executed to increase i would be: 1. Copy value of i to register eax Since busy is now true B will get stuck in the loop ... 2. Increase value of register by 1 until a new thread switch, we assume A continue: 3. Copy value of register eax to i A5 A6 B2 B3 B4 B5 B6 We first assume one CPU and that thread A completed before thread B busy t t f t t t t f starts. Indicating the instruction order by thread name and i 0 1 1 1 1 1 2 2 instruction number we have: When A6 has been executed B can continue (once B is scheduled again). A1 A2 A3 B1 B2 B3 The result is now correct again. One problem we can see here is that i 0 0 0 1 1 1 2 thread B is wasting a lot of CPU time. B occupies the CPU to determine when to stop waiting, this is called "busy−wait" and is a waste of CPU Executing the instructions you find that the final value of i is 2. time. Really bad. But that’s not all. Since it is increased two times this is correct and expected. Let us now consider the code again, but now we use our brains to Next we assume a timer interrupt causing a thread switch between insert interrupts at the most "unlucky" places, between A1 and A2 as instruction A2 and A3. Assuming thread B is picked from the ready well as between B3 and B4: queue the ins ruction sequence become: A1 B1 B2 B3 A2 A3 A4 A5 A6 B4 B5 B6 A1 A2 B1 B2 B3 A3 busy f f f t t t t t t f f f f i 0 0 0 0 0 0 1 i 0 0 0 0 0 0 0 0 1 1 1 1 1 The final value of i become 1. Since we still have the same program With two unlucky thread switches the result is still wrong. Because that increase i twice this is unexpected and wrong. What happened? now we pushed the problem to the busy flag. Now the initial value of Both threads read the initial value of i (A1 and B1), and then A3 that is read by both threads. overwrote the result of B3. We note that the reason for the problem given only one CPU is that we Finally we assume two CPUs ant that the threads run truly simultaneous. get unlucky thread switches, and they are caused by interrupts. We We assume thread B start a fraction of time after A. test a new idea, disable interrupts: A1 A2 A3 thread A and B B1 B2 B3 1 disable interrupts 2 copy i to eax We have a similar result as previously. Both threads read the initial 3 increment eax value of i and B3 will overwrite the result of A3 (loosing it). 4 copy eax to i 5 enable interrupts Using threads a above the result is not deterministic. Clearly not acceptable. A program should always return a deterministic correct Wow, that works great. No unlucky switches. Either the switches happen result. How do we solve the situation? before we read the value of i, or after we already updated it. The section of code from we start using the thread−common variable i to First, we try to let each thread put up a sign "I’m modifying i, the point we are done are called a "critical section" (the time during you’ll have to wait". The new program: which switches must be prevented). main Unfortunately we still have a host of problems: int i = 0; bool busy = false; Consider we need to do something more complicated, and do it often: start thread A start thread B insert_unique(list, x) wait for threads to finish { printf("value of i: %d\n", i); disable interrupts 1

  2. synchronization.txt synchronization.txt Feb 2 2009 1:10 Page 3����������� Feb 2 2009 1:10 Page 4����������� if (!find(list, x)) put thread on wait queue |__ lock |__ also critical section append(list, x) switch to other thread | | enable interrupts busy = true; | / } enable interrupts / Observe we must prevent switches during the entire operation, during copy i to eax \ find we traverse the pointers of the linked list, and if some thread increment eax > "main" critical section is half−way to inserting something one pointer is bound to be wrong. copy eax to i / Also if some thread could insert x just after we determined it was not in the list we would insert a duplicate. disable interrupts \ move one thread from wait | \ Disabling interrupts for the amount of time needed to traverse the list queue to ready queue > unlock > also critical section will prevent also keystrokes, network packets, hard−drive access busy = false; | / etc... everything that depends on interrupts to signal completion. The enable interrupts / computer will "stop responding". Very bad. Now we have a lock solution that works good on single CPU. Since this Also, disabling interrupts should be something only the OS can do, puts the threads asleep while waiting we call them "sleeplocks". Using since if user−programs could do it one program could "hang" the the lock around code sections guarantees mutual exclusion (sometimes computer. But we want to be able to use threads safely also in user locks are called mutex’es). Only one of the code sections can be programs. executed "simultaneous". Third, consider this solution on a multiprocessor. It will not prevent Using a while loop is essential, since someone else may enter the the other processors to access i during the critical section unless critical section while a "unlocked" thread is on the ready queue, so all other processors is stopped somehow. And stopping all other it must wait again. processors would be a heavy penalty. Note that we have a two−level locking. The disabled interrupts Let us now combine the ideas to see if some problem is solved: guarantees atomic modification of "busy", that guarantee atomic modification our user code. But the user code runs with interrupts thread A and B enabled. 0 disable interrupts \ 1 while (busy) | −−− ; > lock 2 busy = true; | Consider a comparison: A bathroom without a lock (or broken lock), so 3 enable interrupts / you can not lock the door while doing your business. So you put up a sign on the door when you enter that reads "BUSY", and 4 copy i to eax \ take it down when you leave. 5 increment eax > critical section For this to work without embarrassment it puts heavy requirements on 6 copy eax to i / the involved parties: 7 disable interrupts \ If someone enters the room without checking the sign embarrassment 8 busy = false; > unlock may occur... 9 enable interrupts / If some joker takes the sign down embarrassment may occur... If someone forgets to put the sign up embarrassment may occur... Now we prevent switches during the time we access the busy flag. If someone forgets to take the sign down embarrassment may occur because of too long Since this is a very brief operation interrupts will not be disabled waiting... for long. Interrupts are now enabled during (the possibly heavy) computation in the critical section. Also consider the situation when the bathroom (critical section) has many doors. The sign must be checked no matter which door is used. And The problem with only OS should be able to access interrupts can be there can be only one sign for this bathroom, if several signs occur solved by letting the OS provide the lock and unlock code as a OS the algorithm breaks (may be checking wrong sign). function, taking the address of the busy variable (the sign). It will always enable interrupts again before user program restarts. Thus the programmer must use the lock and unlock code correct at every access. And other threads still can not modify the variable i as long as they execute the lock code before and the unlock code after the −−− modification attempt. But what about multiprocessors (the today and even more the future)? Unfortunately we still have problems. At line 1 interrupts are Well, clever hardware engineers have invented two special "atomic" disabled, so no switch will occur. Thus, reaching this code when the instructions (short instructions that are done without any interrupt busy flag is true will enter an infinite loop. Very bad. Even worse or other CPU can intervene, they are "locked"). Two variants exists, than a busy−loop. And the problem with multiprocessors remain. test−and−set and atomic−swap. They would equate the following code: Fortunately the infinite loop can be solved: int test_and_set(int* adr) thread A and B { disable interrupts \ int ret = *adr; while (busy) | \ *adr = 1; /* true */ 2

Recommend


More recommend