RU/2: æÏÒÕÍ. ïÂÝÅÎÉÅ ÐÏÌØÚÏ×ÁÔÅÌÅÊ É ÒÁÚÒÁÂÏÔÞÉËÏ× OS/2 (eCS). : ïÔ×ÅÔÉÔØ ÎÁ ÓÏÏÂÝÅÎÉÅ
éÍÑ:
e-mail:
FIDO:
Home page:
ÓÏÈÒÁÎÉÔØ ÄÁÎÎÙÅ Ï ×ÁÓ
ôÅÍÁ:
> Example 3: Race Conditions - When two threads have the access to the same memory space > > The first basic problem of multithreading can be illustrated with these fragments of > two simple tasks of the following program. These two tasks where each can increments > the same global location: > > long counter; > > void thread2(void *x) > > { > long i; > long tmp; > > for (i= 0; i < 1000000; i++) { > > tmp= counter; > tmp++; > counter= tmp; > } > } > > > > void thread3(void *x) > > { > long i; > long tmp; > > for (i= 0; i < 2000000; i++) { > > tmp= counter; > tmp++; > counter= tmp; > } > } > > These two tasks work in this example: > > /*ÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑ* > *Ñ Example 3 Ñ* > *çÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑ÷* > *Ñ Ñ* > *Ñ This program demonstrates the basic problem of concurrency. Ñ* > *Ñ Two threads concurrently modify a global location, and a mistake Ñ* > *Ñ ensues. Ñ* > *Ñ Ñ* > *Ñ Each of the two threads increments a global location a Ñ* > *Ñ set number of times. If the two threads ran serially, the value Ñ* > *Ñ of the counter would be the sum of the two increments at the Ñ* > *Ñ end of execution. However, because a thread can be rescheduled Ñ* > *Ñ in the midst of updating the counter, some updates get lost. Ñ* > *Ñ Ñ* > *ÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑ*/ > > #include <stdio.h> > #include <process.h> > > #define INCL_DOSPROCESS > #define STACK_SIZE 0x8000 > #include <os2.h> > > > /* > * Function declarations for the threads. > */ > void thread2(void *); > void thread3(void *); > > /* > * Declare our global counter variable. This is the location which > * the two threads will be fighting over. > */ > long counter; > > /*ÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑ* > *Ñ Main() Ñ* > *ÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑ*/ > void main() > { > TID thread2Tid; > TID thread3Tid; > > counter= 0; > printf("Program starts:\n"); > printf(" counter= %d\n", counter); > printf(" [Threads start..."); fflush(stdout); > > /* > * Start the threads: > */ > thread2Tid= _beginthread( > thread2, /* Address of function */ > NULL, /* Don't really give stack */ > STACK_SIZE, /* Size of stack needed */ > (PVOID) 0); /* Message to thread */ > > thread3Tid= _beginthread( > thread3, /* Address of function */ > NULL, /* Don't really give stack */ > STACK_SIZE, /* Size of stack needed */ > (PVOID) 0); /* Message to thread */ > > > /* > * Wait for both threads to complete. > */ > DosWaitThread(&thread2Tid, DCWW_WAIT); > DosWaitThread(&thread3Tid, DCWW_WAIT); > > /* > * Now that they're done, let's see what the counter reads. > * If we had called the functions in serial, we would expect the > * result to be 3000000. We will find that it is less, because > * some updates have been lost. > */ > printf("end]\n"); > printf(" counter= %d\n", counter); > > } > > /*ÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑ* > *Ñ thread2() Ñ* > *çÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑ÷* > *Ñ Ñ* > *Ñ thread2 increments the global location "counter" 1000000 Ñ* > *Ñ times. Ñ* > *Ñ Ñ* > *ÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑ*/ > > void thread2(void *foo) > > { > long i; > long tmp; > > for (i= 0; i < 1000000; i++) { > tmp= counter; > tmp++; > counter= tmp; > } > } > > > /*ÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑ* > *Ñ thread3() Ñ* > *çÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑ÷* > *Ñ Ñ* > *Ñ thread3 increments the global location "counter" 2000000 Ñ* > *Ñ times. Ñ* > *Ñ Ñ* > *ÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑ*/ > > void thread3(void *foo) > > { > long i; > long tmp; > > for (i= 0; i < 2000000; i++) { > tmp= counter; > tmp++; > counter= tmp; > } > } > > If these routines run serially, the counter will be greater than by 3,000,000 after both > routines have run. One might expect the same behavior when they are two different > threads, but we would be surprised. > > If one tries to run this program, one will find that the number varies each time that it > runs. What is the reason? Remember that with preemptive multitasking, a process (or > in this case a thread) can be interrupted at any point to run another thread(see and > read again: http://os2.in.ru/forum/m019498.html). > > Remember: thread #1 within a program is the thread of the main routine > > Suppose thread 2 is interrupted after the statement > > tmp= counter; > > and thread 3 is run for a while, after which thread 2 is resumed. One might have the > following interleaving of instructions: > > thread 2 .................................thread 3 > > tmp= counter; > ....................................................tmp= counter; > ....................................................tmp++; > ....................................................counter= tmp; > .................................................................................. > ....................................................counter= tmp; > tmp++; > counter= tmp; > > Thread 2 gets the value of counter into a local(!) variable, then thread 3 takes over, > incrementing 'counter'. When thread 2 resumes, however, it still has an old value of > 'counter' saved in 'tmp', and it is this old value that it increments and stores. > > Thus, all of the increments that thread 3 did are lost. This type of 'bug' is called a > 'timing bug' or a 'race condition', indicating, that the bug appears only under certain timing > conditions. Such bugs can be extraordinarily difficult to find. Because they often are > intermittent, they are difficult to debug. > > Further, even when a timing bug is reproducible, using a debugger might alter the timing > of the program sufficiently that the bug no longer occurs. > > What this example has shown is that the sequence: > > tmp= counter; > tmp++; > counter= tmp; > > is not atomic. An 'atomic operation' is one that runs from start to finish without anything > else happening. Atomicity is relative to how we define "anything else happening". In > absolute terms, the only things that are truly atomic are the quantum state transitions > of fundamental particles; however, this assumes a rather broad definition of "anything > else happening". > > It is generally useful to limit the scope to the CPU that we are running on. In this context, > most instructions are atomic. On some hardware, though, there are even some instructions > of sufficient complexity that they can be interuppted in the middle by a hardware > interrupt or a page fault. If we replace our three line sequence by the equivalent: > > counter= counter + 1; > > the program can be to work as expected. This is because the code is optimized into one > atomic instruction that increments 'counter'. This optimization depends on the compiler, > so it might or might not translate into one instruction. A sufficiently brilliant compiler > might even be able to optimize the three line sequence into one line, recognizing that the > 'tmp' local variable is extraneous. > > In the next example 4 we will see a solution of this problem with defined 'critical > sections'. > > > > >
_, _, _, _, _ _, _,_
(_ | / \ |\ | / \ |_/
, ) | , \ / | \| \ / | \
~ ~~~ ~ ~ ~ ~ ~ ~
Programmed by
Dmitri Maximovich
,
Dmitry I. Platonoff
,
Eugen Kuleshov
.
25.09.99 (c) 1999,
RU/2
. All rights reserved.
Rewritten by
Dmitry Ban
. All rights ignored.