Windows Thread Synchronization 10

 

 

 

 

Concurrency and Race Conditions

 

Race condition happens in many cases not just for threads and processes. A race condition occurs when two threads access a shared variable at the same time (concurrent). The first thread reads or accesses the variable, and at the 'same' time the second thread reads the same value from the variable. Then the first thread and second thread perform their operations such as write, on the same value, and they race to see which thread can write the value last to the shared variable. The value of the thread that writes its value last is preserved, because the thread is writing over the value that the previous thread wrote and of course the first thread which wrote first will have incorrect result though the process was completed successfully. Each thread is allocated a predefined period of time to execute on a processor using such as round robin, interleaving and other methods. When the time slice that is allocated for the thread expires, the thread's context is saved until its next turn on the processor (context switching), and the processor begins the execution of the next thread.

The reason for this is that the operating system decides which thread gets executed first. The order and timing in which the threads start are not all that important. So, a thread that is given 'least priority' for example, by the operating system gets executed last. The most common symptom of a race condition is unpredictable values of variables that are shared between multiple threads. This results from the unpredictability of the order in which the threads execute. Sometime one thread wins, and sometime the other thread wins. At other times, execution works correctly. Also, if each thread is executed separately, the variable value behaves correctly. The race condition is a well known problem that is very difficult to debug. The following Figure tries to demonstrate the race condition, showing some possible interleaving of threads will results in an undesired final computation values.

 

Concurrency and Race Conditions illustration which produce incorrect result

 

 

Can you imagine the result if there are more than two threads which are racing each other to access the shared resources such as in multithreading?

 

Race Condition Program Example

 

The following code example tries to demonstrate the race condition (without any synchronization mechanism).

Create a new empty Win32 console application project. Give a suitable project name and change the project location if needed.

 

Race Condition Program Example: Creating new console mode application C++ project

 

Then, add the source file and give it a suitable name.

 

Race Condition Program Example: Adding new C++ source file

 

Next, add the following source code.

 

#include <windows.h>

#include <stdio.h>

 

const DWORD numThreads = 4;

 

DWORD WINAPI helloFunc(LPVOID arg)

{

      // The call to the wprintf() will affect the thread time execution

      wprintf(LRetard program, I'm thread %u\n, GetCurrentThreadId());

      // This is a dummy sleep to simulate tasks to be completed.

      // The value also will affect the thread time execution

      // You may want to test different Sleep() values...

      Sleep(1000);

      return 0;

}

 

int wmain()

{

      HANDLE hThread[numThreads];

      DWORD dwThreadID, dwEvent, i;

 

      for(int i=0;i<numThreads;i++)

      {

            hThread[i] = CreateThread(NULL,0,(LPTHREAD_START_ROUTINE)helloFunc,(LPVOID)dwThreadID,0,&dwThreadID);

 

            if(hThread[i] != NULL)

                  wprintf(LCreateThread() is OK, thread ID is %u\n, dwThreadID);

            else

                  wprintf(LCreateThread() failed, error %u\n,GetLastError());

      }

     

      // Waits until one or all of the specified objects are

      // in the signaled state or the time-out interval elapses.

      // 3rd param - TRUE, the function returns when the state of all objects in the lpHandles array is signaled.

      // If FALSE, the function returns when the state of any one of

      // the objects is set to signaled. In the latter case, the return value

      // indicates the object whose state caused the function to return.

      // 4th param - If INFINITE, the function will

      // return only when the specified objects are signaled.

      dwEvent = WaitForMultipleObjects(numThreads,hThread,FALSE,INFINITE);

 

      wprintf(L\n);

 

      switch (dwEvent)

    {

        // hThread[0] was signaled

        case WAIT_OBJECT_0 + 0:

            // TODO: Perform tasks required by this event

            wprintf(LFirst event was signaled...\n);

            break;

        // hThread[1] was signaled

        case WAIT_OBJECT_0 + 1:

            // TODO: Perform tasks required by this event

            wprintf(LSecond event was signaled...\n);

            break;

        // hThread[2] was signaled

        case WAIT_OBJECT_0 + 2:

            // TODO: Perform tasks required by this event

            wprintf(LThird event was signaled...\n);

            break;

            // hThread[3] was signaled

        case WAIT_OBJECT_0 + 3:

            // TODO: Perform tasks required by this event

            wprintf(LFourth event was signaled...\n);

            break;

        case WAIT_TIMEOUT:

            wprintf(LWait timed out...\n);

            break;

        // Return value is invalid.

        default:

            wprintf(LWait error %d\n, GetLastError());

            ExitProcess(0);

    }

      wprintf(L\n);

     

      for(i = 0;i<4;i++)

      {

            if(CloseHandle(hThread[i]) != 0)

                  wprintf(LClosing the hThread[%d] handle is OK...\n, i);

            else

                  wprintf(LFailed to close the hThread[%d] handle, error %u...\n, GetLastError());

      }

      return 0;

}

 

Build and run the project. The following are the sample outputs when the program was run many times.

 

Race Condition Program Example: A sample output demonstrating the race condition

 

Race Condition Program Example: Another sample output showing the race condition

 

The following are sample outputs when we change the Sleep(1000); to the smaller value, Sleep(500);

 

Race Condition Program Example: A variation of the race condition

 

Race Condition Program Example: Race condition in action

 

Race Condition Program Example: unpredictable and undeterministic output

 

By editing the following part, the output should be clearer.

 

      for(int i=0;i<numThreads;i++)

      {

            hThread[i] = CreateThread(NULL,0,(LPTHREAD_START_ROUTINE)helloFunc,(LPVOID)dwThreadID,0,&dwThreadID);

 

            if(hThread[i] != NULL)

                  wprintf(LCreateThread() is OK, #%d thread ID is %u\n, i, dwThreadID);

            else

                  wprintf(LCreateThread() failed, error %u\n, GetLastError());

      }

 

The following screenshot is a sample output.

 

Race Condition Program Example: A better race condition demo

 

Next, add/edit some part of the code as shown below, demonstrating the threads is doing some tasks on the global variable x.

 

#include <windows.h>

#include <stdio.h>

 

// Global variable

// May use the 'volatile' keyword instead of 'const' to avoid

// the compiler optimization especially for the Release version

const DWORD numThreads = 4;

DWORD x = 0;

 

DWORD WINAPI helloFunc(DWORD arg)

{

      // The call to the wprintf() will affect the thread time execution

      wprintf(LThread %u, arg = %u\n, GetCurrentThreadId(), arg);

      // Try updating the global variable, x

      x = x + arg;

      wprintf(Lx = %u\n, x);

      // This is a dummy sleep to simulate tasks to be completed.

      // The value also will affect the thread time execution

      // You may want to test different Sleep() values...

      Sleep(1000);

      return 0;

}

 

int wmain()

{

      HANDLE hThread[numThreads];

      DWORD dwThreadID, dwEvent, i;

 

      for(int i=0;i<numThreads;i++)

      {

            hThread[i] = CreateThread(NULL,0,(LPTHREAD_START_ROUTINE)helloFunc,(LPVOID)i,0,&dwThreadID);

 

            if(hThread[i] != NULL)

                  wprintf(LCreateThread() is OK, #%d thread ID is %u\n, i, dwThreadID);

            else

                  wprintf(LCreateThread() failed, error %u\n,GetLastError());

      }

     

      // Waits until one or all of the specified objects are

      // in the signaled state or the time-out interval elapses.

      // 3rd param - TRUE, the function returns when the state of all objects in the lpHandles array is signaled.

      // If FALSE, the function returns when the state of any one of

      // the objects is set to signaled. In the latter case, the return value

      // indicates the object whose state caused the function to return.

      // 4th param - If INFINITE, the function will

      // return only when the specified objects are signaled.

      dwEvent = WaitForMultipleObjects(numThreads,hThread,FALSE,INFINITE);

 

      wprintf(L\n);

 

      switch (dwEvent)

    {

        // hThread[0] was signaled

        case WAIT_OBJECT_0 + 0:

            // TODO: Perform tasks required by this event

            wprintf(LFirst event was signaled...\n);

            break;

        // hThread[1] was signaled

        case WAIT_OBJECT_0 + 1:

            // TODO: Perform tasks required by this event

            wprintf(LSecond event was signaled...\n);

            break;

        // hThread[2] was signaled

        case WAIT_OBJECT_0 + 2:

            // TODO: Perform tasks required by this event

            wprintf(LThird event was signaled...\n);

            break;

            // hThread[3] was signaled

        case WAIT_OBJECT_0 + 3:

            // TODO: Perform tasks required by this event

            wprintf(LFourth event was signaled...\n);

            break;

        case WAIT_TIMEOUT:

            wprintf(LWait timed out...\n);

            break;

        // Return value is invalid.

        default:

            wprintf(LWait error %d\n, GetLastError());

            ExitProcess(0);

    }

      wprintf(L\n);

     

      for(i = 0;i<4;i++)

      {

            if(CloseHandle(hThread[i]) != 0)

                  wprintf(LClosing the hThread[%d] handle is OK...\n, i);

            else

                  wprintf(LFailed to close the hThread[%d] handle, error %u...\n, GetLastError());

      }

      return 0;

}

 

Rebuild and re-run the program several times. The following screenshots show the sample outputs.

 

Race Condition Program Example: sample output with thread doing something

 

Race Condition Program Example: Another sample output with thread doing something

 

Race Condition Program Example: More sample output with thread doing something

 

 

Race Condition Program Example: Sample output with thread doing something but still cannot be predicted

 

Race Condition Program Example: sample output with threads doing something in unordered manner

 

Race Condition Program Example: sample output showing threads not in the order as in the program

 

Race Condition Program Example: sample output of the unordered thread

 

Race Condition Program Example: sample output with threads flow differently

 

Can you see the 'funny' outputs mainly the values of the global variable x? The outputs are not consistent, non-deterministic and time sensitive. Sometimes the outputs are in the sequence as expected; however, many times the outputs are not as expected. Keep in mind that the program (thread execution) runs without 'error'.

 

 

 

< Thread Synchronization 9 | Thread Synchronization Programming | Win32 Programming | Thread Synchronization 11 >