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


 

 

 

MODULE AA1

PROCESSES AND THREADS: SYNCHRONIZATION

Part 5: Program Examples

 

 

 

 

What are in this Module?

  1. Using Event Objects

  2. Using Critical Section Objects

  3. Using Timer Queues

  4. Using Waitable Timer Objects

  5. Using Singly Linked Lists

  6. Program examples on:

  • Using event objects.

  • Using critical section objects.

  • Using timer queues.

  • Using waitable timer objects.

  • Using singly linked lists.

My Training Period: xx hours. Before you begin, read some instruction here. Functions and structure used in the program examples were dumped at Win32 functions & structures.

 

 

 

 

 

 

 

 

 

 

 

 

The Win32 programming 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.

 

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

         // (LPCWSTR)"MasterThWriteEvent"

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

}

 

A sample output:

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

The following output sample run using VC++ Express Edition with Windows SDK installed.

 

 

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;

}

 

A sample output:

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

    // create the timer queue...

    hTimerQueue = CreateTimerQueue();

    if (!hTimerQueue)

    {

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

       // may just return/exit with error code...

        return 2;

    }

    else

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

    // set a timer to call the timer routine in 10 seconds...

    if (!CreateTimerQueueTimer(&hTimer, hTimerQueue, (VOID CALLBACK)TimerRoutine, &arg, 10000, 0, 0))

    {

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

       // may just return/exit with error code...

       return 3;

    }

    else

       printf("CreateTimerQueueTimer() is OK and do the related task...\n");

 

    // TODO: Do other useful work here...

 

    printf("Call timer routine in 10 seconds...\n");

 

    // wait for the timer-queue thread to complete using an event object. The thread will signal the event at that time...

    if (WaitForSingleObject(gDoneEvent, INFINITE) != WAIT_OBJECT_0)

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

    else

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

    // delete all timers in the timer queue...

    if (!DeleteTimerQueue(hTimerQueue))

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

    else

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

    return 0;

}

 

A sample output:

Process & Thread synchronization C program example: Timer queues

 

Using Waitable Timer Objects

 

The following example creates a timer that will be signaled after a 10 second delay. First, the code uses the CreateWaitableTimer() function to create a waitable timer object. Then it uses the SetWaitableTimer() function to set the timer. The code uses the WaitForSingleObject() function to determine when the timer has been signaled.

// for WinXp

#define _WIN32_WINNT 0x0501

#include <windows.h>

#include <stdio.h>

#include <windows.h>

#include <stdio.h>

 

int main()

{

    HANDLE hTimer = NULL;

    LARGE_INTEGER liDueTime;

    liDueTime.QuadPart=-100000000;

    // create a waitable timer.

    hTimer = CreateWaitableTimer(NULL, TRUE, "WaitableTimer");

    if (!hTimer)

    {

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

return 1;

    }

    else

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

            // next step...

printf("Waiting for 10 seconds...\n");

    // set a timer to wait for 10 seconds.

    if (!SetWaitableTimer(hTimer, &liDueTime, 0, NULL, NULL, 0))

    {

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

return 2;

    }

    else

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

    // wait for the timer.

    if (WaitForSingleObject(hTimer, INFINITE) != WAIT_OBJECT_0)

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

    else

printf("Timer was signaled.\n");

    return 0;

}

 

A sample output:

Process & Thread synchronization C program example: Waitable timer

 

Using Singly Linked Lists

 

The following example uses the InitializeSListHead() function to initialize a singly linked list and the InterlockedPushEntrySList() function to insert 10 items. The example uses the InterlockedPopEntrySList() function to remove 10 items and the InterlockedFlushSList() function to verify that the list is empty.

// for WinXp

#define _WIN32_WINNT 0x0501

#include <windows.h>

#include <stdio.h>

#include <malloc.h>

 

// structure to be used for a list item. Typically, the first member is of type SLIST_ENTRY. Additional members are used for data.

// here, the data is simply a signature for testing purposes.

typedef struct _PROGRAM_ITEM {

    SLIST_ENTRY ItemEntry;

    ULONG Signature;

} PROGRAM_ITEM, *PPROGRAM_ITEM;

 

int main()

{

    ULONG Count;

    PSLIST_ENTRY FirstEntry, ListEntry;

    SLIST_HEADER ListHead;

    PPROGRAM_ITEM ProgramItem;

    // initialize the list header.

    InitializeSListHead(&ListHead);

    // insert 10 items into the list.

    for (Count = 1; Count <= 10; Count += 1)

    {

        ProgramItem = (PPROGRAM_ITEM)malloc(sizeof(*ProgramItem));

        ProgramItem->Signature = Count;

        FirstEntry = InterlockedPushEntrySList(&ListHead, &ProgramItem->ItemEntry);

    }

    // remove 10 items from the list.

    for (Count = 10; Count >= 1; Count -= 1)

    {

        ListEntry = InterlockedPopEntrySList(&ListHead);

        free(ListEntry);

    }

    // flush the list and verify that the items are gone.

    ListEntry = InterlockedFlushSList(&ListHead);

    FirstEntry = InterlockedPopEntrySList(&ListHead);

    if (FirstEntry != NULL)

    {

        printf("InterlockedPopEntrySList() failed, error: List is not empty.");

    }

    else

       printf("InterlockedPopEntrySList() is OK, list is empty.\n");

    return 0;

}

 

A sample output:

Process & Thread synchronization C program example: Singly Link list

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

Further reading and digging:

 

  1. Microsoft Visual C++, online MSDN.

  2. Structure, enum, union and typedef story can be found C/C++ struct, enum, union & typedef.

  3. For Multibytes, Unicode characters and Localization please refer to Locale, wide characters & Unicode (Story) and Windows users & groups programming tutorials (Implementation).

  4. Windows data types are Windows data types.

  5. Check the best selling C / C++ and Windows books at Amazon.com.

 

 

 

 

 

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


 

C & C++ Programming Tutorial | C Programming Practice