RU/2: æÏÒÕÍ. ïÂÝÅÎÉÅ ÐÏÌØÚÏ×ÁÔÅÌÅÊ É ÒÁÚÒÁÂÏÔÞÉËÏ× OS/2 (eCS). : Multithreading (XI)


óÐÉÓÏË ÓÏÏÂÝÅÎÉÊ | îÁÐÉÓÁÔØ ÎÏ×ÏÅ | ïÔ×ÅÔÉÔØ ÎÁ ÓÏÏÂÝÅÎÉÅ | äÏÍÏÊ ðÏÉÓË:
ðÒÅÄÙÄÕÝÅÅ ÓÏÏÂÝÅÎÉÅ | óÌÅÄÕÀÝÅÅ ÓÏÏÂÝÅÎÉÅ
From : ???
To : All
Subj : Multithreading (XI)

Example 4.1 and 4.2 : How to handle the 'timing bug' or 'race condition'

However, let's assume that the increment (see :Example 3 in Multithreading (X):
m019837.html) takes several instructions(the safest assumption
anyway). Such a sequence of instructions, one which is sensitive to scheduling, is called
a 'critical section'. How this problem will be solved?

We must define the critical section atomic relative to other, possible conflicting,
instruction sequences.

The most brute force way to do this is demonstrated in exmaple 4.1. Here we surround
the critical section with calls to DosEnterCritSec() and DosExitCritSec(). During the period
after a call to DosEnterCritSec() and before a call to DosExitCritSec(), no other thread in the
same process will be scheduled(!).

The critical section thus is rendered atomic relative to other threads in the same process.
The only problem with using this approach is that all(!) other threads in the program are
prevented from running.

Even threads that do not conflict with the critical section are prevented from running!

The worse case is that the program runs in a deadlock.

Therefore this method should be avoided and replace by the method in example 4.2.

Now let us see example 4.1:

/*ÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑ*
*Ñ Example 4.1 Ñ*
*çÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑ÷*
*Ñ Ñ*
*Ñ This program uses DosEnterCritSec() and DosExitCritSec() to Ñ*
*Ñ show how access to global resources can be serialized by Ñ*
*Ñ disabling rescheduling during critical sections of code. Ñ*
*Ñ Ñ*
*ÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑ*/

#include <stdio.h>
#include <process.h>

#define INCL_DOSPROCESS
#include <os2.h>

#define STACK-SIZE 0x8000

/*
* 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.
* Having used Dos<Enter,Exit>CritSec(), we should get the
* expected 3000000 now.
*/
printf("end]\n");
printf(" counter= %d\n", counter);

}

/*ÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑ*
*Ñ thread2() Ñ*
*çÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑ÷*
*Ñ Ñ*
*Ñ thread2 increments the global location "counter" 1000000 Ñ*
*Ñ times, protecting the update with DosEnterCritSec(). Ñ*
*Ñ Ñ*
*ÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑ*/

void thread2(void *foo)

{
long i;
long tmp;

for (i= 0; i < 1000000; i++) {

/*
* DosEnterCritSec() will prevent other threads in this
* process from running, until we call DosExitCritSec().
*/
DosEnterCritSec();

/*
* We now have the process to ourselves. No one else can run.
*/
tmp= counter;
tmp++;
counter= tmp;

/*
* We're done accessing the global location, so we can allow
* other threads to run now. DosExitCritSec() does this.
*/
DosExitCritSec();
}
}


/*ÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑ*
*Ñ thread3() Ñ*
*çÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑ÷*
*Ñ Ñ*
*Ñ thread3 increments the global location "counter" 2000000 Ñ*
*Ñ times, protecting the update with DosEnterCritSec(). Ñ*
*Ñ Ñ*
*ÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑ*/

void thread3(void *foo)

{
long i;
long tmp;

for (i= 0; i < 2000000; i++) {
/*
* DosEnterCritSec() will prevent other threads in this
* process from running, until we call DosExitCritSec().
*/
DosEnterCritSec();

/*
* We now have the process to ourselves. No one else can run.
*/
tmp= counter;
tmp++;
counter= tmp;

/*
* We're done accessing the global location, so we can allow
* other threads to run now. DosExitCritSec() does this.
*/
DosExitCritSec();
}
}

Example 4.2 : The problem solved with the method of mutual exclusion("mutex")

What is wanted for this particular problem is the application of mutual exclusion. Here,
a mutual exclusion semaphore is used to serialize the access to the global resource(in this
case, the variable 'counter').

Serializing access to a resource means that only one thread at a time will be able to
access the resource - in other words, they access the resource in serial. In example 4.2
wille be shown how the mutex semaphores are used to solve the problem.

Now, the critical section is atomic not to all other tasks in the system but to any other
code sequences that conflict with the critical section. Note that all code sequences that
access the resource must request and obtain the semaphore before using the resource.

If an uncooperative thread fails to obtain the semaphore before accessing the resource,
the program might behave incorrectly.

The theme mutual exclusion semaphores are dicussend more detailed in a further lession.

Now see example 4.2:

/*ÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑ*
*Ñ Example 4.2 Ñ*
*çÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑ÷*
*Ñ Ñ*
*Ñ This program uses a mutual exclusion semaphore to serialize Ñ*
*Ñ access to a global variable. Ñ*
*Ñ Ñ*
*ÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑ*/

#include <stdio.h>
#include <process.h>

#define INCL_DOSPROCESS
#define INCL_DOSSEMAPHORES
#include <os2.h>

#define STACK_SIZE 0x8000

/*
* Function declarations for the threads.
*/
void thread2(void *);
void thread3(void *);

/*
* Declare our global counter variable:
*/
long counter;

/*
* Declare a mutex semaphore to protect the variable.
*/
HMTX lockCounter;


/*ÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑ*
*Ñ Main() Ñ*
*ÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑ*/
void main()
{
TID thread2Tid;
TID thread3Tid;

/*
* Create our mutex semaphore:
*/
DosCreateMutexSem(
NULL, /* We don't need a name */
&lockCounter, /* Place to return handle */
0, /* No options */
FALSE); /* Initially unowned */

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);

/*
* Let's see what the counter reads. It should read the expected
* 3000000, since we've proteced the updates with a mutual exclusion
* semaphore.
*/
printf("end]\n");
printf(" counter= %d\n", counter);

}

/*ÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑ*
*Ñ thread2() Ñ*
*çÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑ÷*
*Ñ Ñ*
*Ñ thread2 increments the global location "counter" 1000000 Ñ*
*Ñ times, protecting the update with a mutual exclusion semaphore. Ñ*
*Ñ Ñ*
*ÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑ*/

void thread2(void *foo)

{
long i;
long tmp;

for (i= 0; i < 1000000; i++) {

/*
* Before updating the global variable we need to lock
* by requesting the mutex semaphore.
*/
DosRequestMutexSem(
lockCounter, /* Semaphore Handle */
SEM_INDEFINITE_WAIT); /* Wait till we get it */

/*
* We own the lock now. If anyone else requests it they will
* block until we release it.
* We can therefore do our update now.
*/
tmp= counter;
tmp++;
counter= tmp;

/*
* We're done with the update. Release the lock so someone
* else can get it.
*/
DosReleaseMutexSem(lockCounter);
}
}


/*ÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑ*
*Ñ thread3() Ñ*
*çÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑ÷*
*Ñ Ñ*
*Ñ thread3 increments the global location "counter" 2000000 Ñ*
*Ñ times, protecting the update with a mutual exclusion semaphore. Ñ*
*Ñ Ñ*
*ÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑ*/

void thread3(void *foo)

{
long i;
long tmp;

for (i= 0; i < 2000000; i++) {

/*
* Before updating the global variable we need to lock
* by requesting the mutex semaphore.
*/
DosRequestMutexSem(
lockCounter, /* Semaphore Handle */
SEM_INDEFINITE_WAIT); /* Wait till we get it */

/*
* We own the lock now. If anyone else requests it they will
* block until we release it.
* We can therefore do our update now.
*/
tmp= counter;
tmp++;
counter= tmp;

/*
* We're done with the update. Release the lock so someone
* else can get it.
*/
DosReleaseMutexSem(lockCounter);
}
}


In the next lession there are some informations about OS/2 APIs which used in the lessions before.







Wed 21 Jan 2004 13:53 Mozilla/4.61 [en] (OS/2; U)




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.