|< Windows Processes & Threads: Synchronization 4 | Main | Dynamic Link Library, DLL 1 >| Site Index | Download |


 

MODULE AA1

PROCESSES AND THREADS: SYNCHRONIZATION

Part 5: Program Examples

 

 

My Training Period:          hours

 

Note:

..Continuation from previous Module...Program examples compiled using using an empty Win32 console mode application of the Visual C++ .Net (Visual studio .Net 2003).  It is low-level programming and the .Net used is Unmanaged (/clr is not set: Project menu → your_project_name Properties… sub menu → Configuration Properties folder → General subfolder → Used Managed Extension setting set to No).  This also applied to other program examples in other Modules of tenouk.com Tutorial that mentioned “compiled using Visual C++ .Net”.  Other settings are default.  Machine’s OS is standalone Win Xp SP2.  Program examples have been tested for Non Destructive Test :o).  All information recomposed for Win 2000 (NT5.0) above.

 

WARNING

 

Wrongly modified and run the program examples presented here might collapse your Windows machine or disturb its integrity.  So for your Windows machine safety, make sure you fulfill the following conditions:

 

  1. Already read, understood and agreed to tenouk.com small disclaimer.
  2. Must be familiar with Windows OSes mainly NT5 (Windows 2000 above).
  3. Must be fluent in the following C topics: Function, Array, Pointer, String and Structure.
  4. Then, the basic knowledge of the Microsoft C Run-Time (CRT) and Win32 Programming using C.
  5. Do not mess up your system (Get a test machine! And run the program examples on the test machine).
  6. Must understand what you are going to do with the Windows APIs.
  7. Functions and structure used in the program examples were dumped at Win32 functions & structures.
  8. Other related and required information (if any) not available in no. 7 can be found at MSDN Online documentation.

 

Abilities

 

             Able to understand synchronization objects such as mutex, semaphore, event, critical section and waitable timer.

             Able to understand, use, build programs that use synchronization objects.

 

Note: For Multithreaded program examples, you have to set your project to Multithread project type.

 

Index

 

The following simple examples try to demonstrate how to use the synchronization objects:

 

          Using event objects.

          Using critical section objects.

          Using timer queues.

          Using waitable timer objects.

          Using singly linked lists.

 

Using Event Objects

 

Applications use event objects in a number of situations to notify a waiting thread of the occurrence of an event. For example, overlapped I/O operations on files, named pipes, and communications devices use an event object to signal their completion.

In the following example, an application uses event objects to prevent several threads from reading from a shared memory buffer while a master thread is writing to that buffer. First, the master thread uses the CreateEvent() function to create a manual-reset event object. The master thread sets the event object to non-signaled when it is writing to the buffer and then resets the object to signaled when it has finished writing. Then it creates several reader threads and an auto-reset event object for each thread. Each reader thread sets its event object to signaled when it is not reading from the buffer.

 

 

#define NUMTHREADS 4

 

HANDLE hGlobalWriteEvent;

 

void CreateEventsAndThreads(void)

{

    HANDLE hReadEvents[NUMTHREADS], hThread;

    DWORD i, IDThread;

 

    // Create a manual-reset event object. The master thread sets

    // this to non-signaled when it writes to the shared buffer.

    hGlobalWriteEvent = CreateEvent(

        NULL,         // no security attributes

        TRUE,         // manual-reset event

        TRUE,         // initial state is signaled

        "MasterThWriteEvent"  // object name

        );

 

    if (hGlobalWriteEvent == NULL)

    {

printf("CreateEvent() failed, error: %d.\n", GetLastError());

// error exit...

    }

    else

       printf("CreateEvent(), master thread with manual reset is OK.\n\n");

 

    // Create multiple threads and an auto-reset event object

    // for each thread. Each thread sets its event object to

    // signaled when it is not reading from the shared buffer.

    printf("Multiple threads with auto-reset event object for each thread...\n");

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

    {

        // Create the auto-reset event.

        hReadEvents[i] = CreateEvent(

            NULL,     // no security attributes

            FALSE,    // auto-reset event

            TRUE,     // initial state is signaled

            NULL);    // object not named

 

        if (hReadEvents[i] == NULL)

        {

            printf("CreateEvent() failed.\n");

            // Error exit.

        }

else

printf("CreateEvent() #%d is OK.\n", i);

 

        hThread = CreateThread(NULL, 0,

            (LPTHREAD_START_ROUTINE) ThreadFunction,

            &hReadEvents[i],  // pass event handle

            0, &IDThread);

        if (hThread == NULL)

        {

// Error exit.

              printf("CreateThread() failed, error: %d.\n", GetLastError());

        }

        else

printf("CreateThread() #%d is OK.\n\n", i);

    }

}

 

Before the master thread writes to the shared buffer, it uses the ResetEvent() function to set the state of hGlobalWriteEvent (an application-defined global variable) to non-signaled. This blocks the reader threads from starting a read operation. The master then uses the WaitForMultipleObjects() function to wait for all reader threads to finish any current read operations. When WaitForMultipleObjects() returns, the master thread can safely write to the buffer. After it has finished, it sets hGlobalWriteEvent and all the reader-thread events to signaled, enabling the reader threads to resume their read operations.

 

void WriteToBuffer(void)

{

    DWORD dwWaitResult, i;

 

    // Reset hGlobalWriteEvent to non-signaled, to block readers.

    if (!ResetEvent(hGlobalWriteEvent))

    {

        // Error exit.

    }

 

    // Wait for all reading threads to finish reading.

    dwWaitResult = WaitForMultipleObjects(

        NUMTHREADS,   // number of handles in array

        hReadEvents,  // array of read-event handles

        TRUE,         // wait until all are signaled

        INFINITE);    // indefinite wait

 

    switch (dwWaitResult)

    {

        // All read-event objects were signaled.

        case WAIT_OBJECT_0:

            // Write to the shared buffer.

            break;

 

        // An error occurred.

        default:

            printf("Wait error: %d\n", GetLastError());

            ExitProcess(0);

    }

 

    // Set hGlobalWriteEvent to signaled.

    if (!SetEvent(hGlobalWriteEvent))

    {

        // Error exit.

    }

 

    // Set all read events to signaled.

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

        if (!SetEvent(hReadEvents[i]))

        {

            // Error exit.

        }

}

 

Before starting a read operation, each reader thread uses WaitForMultipleObjects() to wait for the application-defined global variable hGlobalWriteEvent and its own read event to be signaled. When WaitForMultipleObjects() returns, the reader thread's auto-reset event has been reset to non-signaled. This blocks the master thread from writing to the buffer until the reader thread uses the SetEvent() function to set the event's state back to signaled.

 

void ThreadFunction(LPVOID lpParam)

{

    DWORD dwWaitResult;

    HANDLE hEvents[2];

 

    hEvents[0] = *(HANDLE*)lpParam;  // thread's read event

    hEvents[1] = hGlobalWriteEvent;

 

    dwWaitResult = WaitForMultipleObjects(

        2,            // number of handles in array

        hEvents,      // array of event handles

        TRUE,         // wait till all are signaled

        INFINITE);    // indefinite wait

 

    switch (dwWaitResult)

    {

        // Both event objects were signaled.

        case WAIT_OBJECT_0:

            // Read from the shared buffer.

            break;

 

        // An error occurred.

        default:

            printf("Wait error: %d\n", GetLastError());

            ExitThread(0);

    }

 

    // Set the read event to signaled.

    if (!SetEvent(hEvents[0]))

    {

        // Error exit.

    }

}

 

To see some dummy action, run the following program example.  It just a program skeleton because we don’t have the real read/write operation here.  Don’t forget to run the program second time and see the output difference. Set your project to multithread type.

 

// For WinXp

#define _WIN32_WINNT 0x0501

#include <windows.h>

#include <stdio.h>

 

#define NUMTHREADS 4

 

HANDLE hGlobalWriteEvent;

 

HANDLE hReadEvents[NUMTHREADS], hThread;

DWORD i, IDThread;

 

void ThreadFunction(LPVOID lpParam)

{

    DWORD dwWaitResult;

    HANDLE hEvents[2];

 

    hEvents[0] = *(HANDLE*)lpParam;  // thread's read event

    hEvents[1] = hGlobalWriteEvent;

 

    dwWaitResult = WaitForMultipleObjects(

        2,            // number of handles in array

        hEvents,      // array of event handles

        TRUE,         // wait till all are signaled

        INFINITE);    // indefinite wait

   

    printf("\nIn ThreadFunction()...\n");

    switch (dwWaitResult)

    {

        // Both event objects were signaled.

        case WAIT_OBJECT_0:

printf("Both event objects were signaled.\n");

// Read from the shared buffer...

break;

 

        // An error occurred.

        default:

printf("Wait error: %d\n", GetLastError());

ExitThread(0);

    }

 

    // Set the read event to signaled.

    if (!SetEvent(hEvents[0]))

    {

        printf("SetEvent(), setting the read event to signaled failed.\n");

// Error exit.

    }

    else

       printf("SetEvent(), setting the read event to signaled is OK.\n");

}

 

//===============================================================

void WriteToBuffer(void)

{

    DWORD dwWaitResult, i;

 

    printf("\nIn WriteToBuffer()...\n");

    // Reset hGlobalWriteEvent to non-signaled, to block readers...

    if (!ResetEvent(hGlobalWriteEvent))

     {

printf("ResetEvent(hGlobalWriteEvent) failed.\n");

// Error exit.

    }

    else

printf("ResetEvent(hGlobalWriteEvent) is OK.\n");

 

    // Wait for all reading threads to finish reading...

    dwWaitResult = WaitForMultipleObjects(

        NUMTHREADS,   // number of handles in array

        hReadEvents,  // array of read-event handles

        TRUE,         // wait until all are signaled

        INFINITE);    // indefinite wait

 

    switch (dwWaitResult)

    {

        // All read-event objects were signaled...

        case WAIT_OBJECT_0:

printf("All read-event objects were signaled.\n");

// Write to the shared buffer...

break;

 

        // An error occurred...

        default:

            printf("Wait error: %d\n", GetLastError());

            ExitProcess(0);

    }

 

    // Set hGlobalWriteEvent to signaled...

    if (!SetEvent(hGlobalWriteEvent))

    {

        // Error exit.

    }

 

    // Set all read events to signaled...

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

    {

       if (!SetEvent(hReadEvents[i]))

        {

printf("SetEvent(), setting read event %d to signaled failed.\n", i);

// Error exit.

        }

       else

              printf("SetEvent(), read event %d signaled.\n", i);

     }

}

 

//===============================================================

void CreateEventsAndThreads(void)

{

    // Create a manual-reset event object. The master thread sets

    // this to non-signaled when it writes to the shared buffer...

    printf("In CreateEventsAndThreads()...\n");

    hGlobalWriteEvent = CreateEvent(

        NULL,         // no security attributes

        TRUE,         // manual-reset event

        TRUE,         // initial state is signaled

        "MasterThWriteEvent"  // object name

        );

 

    if (hGlobalWriteEvent == NULL)

    {

printf("CreateEvent() failed, error: %d.\n", GetLastError());

// error exit...

    }

    else

       printf("CreateEvent(), master thread with manual reset is OK.\n\n");

 

    // Create multiple threads and an auto-reset event object

    // for each thread. Each thread sets its event object to

    // signaled when it is not reading from the shared buffer...

    printf("Multiple threads with auto-reset event object for each thread...\n");

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

    {

        // Create the auto-reset event.

        hReadEvents[i] = CreateEvent(

            NULL,     // no security attributes

            FALSE,    // auto-reset event

            TRUE,     // initial state is signaled

            NULL);    // object not named

 

        if (hReadEvents[i] == NULL)

        {

printf("CreateEvent() failed.\n");

// Error exit.

        }

       else

              printf("CreateEvent() #%d is OK.\n", i);

 

        hThread = CreateThread(NULL, 0,

            (LPTHREAD_START_ROUTINE) ThreadFunction,

            &hReadEvents[i],  // pass event handle

            0, &IDThread);

 

        if (hThread == NULL)

        {

// Error exit.

              printf("CreateThread() failed, error: %d.\n", GetLastError());

        }

       else

              printf("CreateThread() #%d is OK.\n\n", i);

    }

}

 

int main()

{     

       CreateEventsAndThreads();

       WriteToBuffer();

       return 0;

}

 

In CreateEventsAndThreads()...

CreateEvent(), master thread with manual reset is OK.

 

Multiple threads with auto-reset event object for each thread...

CreateEvent() #0 is OK.

CreateThread() #0 is OK.

 

CreateEvent() #1 is OK.

CreateThread() #1 is OK.

 

CreateEvent() #2 is OK.

CreateThread() #2 is OK.

 

CreateEvent() #3 is OK.

CreateThread() #3 is OK.

 

 

In WriteToBuffer()...

ResetEvent(hGlobalWriteEvent) is OK.

All read-event objects were signaled.

 

In ThreadFunction()...

Both event objects were signaled.

SetEvent(), setting the read event to signaled is OK.

SetEvent(), read event 0 signaled.

 

In ThreadFunction()...

Both event objects were signaled.

SetEvent(), setting the read event to signaled is OK.

SetEvent(), read event 1 signaled.

 

In ThreadFunction()...

Both event objects were signaled.

SetEvent(), setting the read event to signaled is OK.

SetEvent(), read event 2 signaled.

 

In ThreadFunction()...

Both event objects were signaled.

SetEvent(), setting the read event to signaled is OK.

SetEvent(), read event 3 signaled.

Press any key to continue

 

 

Using Critical Section Objects

 

The following example shows how a thread initializes, enters, and releases a critical section. It uses the InitializeCriticalSectionAndSpinCount(), EnterCriticalSection(), LeaveCriticalSection(), and DeleteCriticalSection() functions.

 

// For WinXp

#define _WIN32_WINNT 0x0501

#include <windows.h>

#include <stdio.h>

 

// Global variable

CRITICAL_SECTION CriticalSection;

 

// Application defined function...

DWORD WINAPI ThreadProc(LPVOID lpParameter)

{

       // TODO: Other tasks...

       printf("In ThreadProc(), application defined function...\n");

       printf("EnterCriticalSection() and LeaveCriticalSection().\n");

      

       // Request ownership of the critical section.

       EnterCriticalSection(&CriticalSection);

 

    // TODO: For example, access the shared resource...

 

    // Release ownership of the critical section.

    LeaveCriticalSection(&CriticalSection);

 

    // TODO: Other tasks...

       return 0;

}

 

int main()

{

       // TODO: Other tasks...

       printf("In main()...\n");

       printf("InitializeCriticalSectionAndSpinCount() and after\n");

       printf("return from function call use DeleteCriticalSection()...\n\n");

 

       // Initialize the critical section one time only...

    if (!InitializeCriticalSectionAndSpinCount(&CriticalSection, 0x80000400))

       printf("InitializeCriticalSectionAndSpinCount() failed, error: %d.\n", GetLastError());

        // just return or other error processing...

    else

printf("InitializeCriticalSectionAndSpinCount() is OK.\n");

  

    // Release resources used by the critical section object.

    DeleteCriticalSection(&CriticalSection);

      

       return 0;

}

 

Process & Thread synchronization C program example: Critical sections

 

Using Timer Queues

 

The following example creates a timer routine that will be executed by a timer-queue thread after a 10 second delay. First, the code uses the CreateEvent() function to create an event object that is signaled when the timer-queue thread completes. Then it creates a timer queue and a timer-queue timer, using the CreateTimerQueue() and CreateTimerQueueTimer() functions, respectively. The code uses the WaitForSingleObject() function to determine when the timer routine has completed. Finally, the code calls DeleteTimerQueue() to clean up.

 

// For WinXp

#define _WIN32_WINNT 0x0501

#include <windows.h>

#include <stdio.h>

 

HANDLE gDoneEvent;

 

VOID CALLBACK TimerRoutine(PVOID lpParam, BOOL TimerOrWaitFired)

{

    if (lpParam == NULL)

    {

        printf("TimerRoutine()'s lpParam is NULL.\n");

    }

    else

    {

        // lpParam points to the argument; in this case it is an int...

        printf("Timer routine called. Parameter is %d.\n", *(int*)lpParam);

    }

 

    SetEvent(gDoneEvent);

}

 

int main()

{

    HANDLE hTimer = NULL;

    HANDLE hTimerQueue = NULL;

    int arg = 123;

 

    // Use an event object to track the TimerRoutine() execution...

    gDoneEvent = CreateEvent(NULL, TRUE, FALSE, NULL);

    if (!gDoneEvent)

    {

printf("CreateEvent() failed, error: %d.\n", GetLastError());

return 1;

    }

    else

       printf("CreateEvent() is OK.\n");