《Windows核心编程》の线程间同步

Windows中线程间同步的方法主要有:事件(Event)、临界区(Critical Section)、互斥量(Mutex)和信号灯(Semaphore)。

 

1)使用事件对象进行线程间同步:

在使用CreateEvent函数创建事件对象时,将bManualReset参数设置为FALSE,然后在需要独占操作的代码前面加上一个WaitForSingleObject函数,后面加上一个SetEvent即可。

由于bManualReset参数为FALSE,这样当某个线程等待到Event后,Event对象的状态马上就变为复位状态,这样其他线程执行到WaitForSingleObject时就全部处于等待中了,当活动的线程操作完毕后,执行SetEvent函数,Event对象的状态才恢复到置位,这样其他等待的线程才会有一个能继续操作。

 

SetEvent函数原型如下:

HANDLE WINAPI CreateEvent(

  __in_opt  LPSECURITY_ATTRIBUTES lpEventAttributes, //安全描述符

  __in      BOOL bManualReset, //指定事件对象是否需要手动复位

  __in      BOOL bInitialState, //指定事件对象创建时的初始状态,为TRUE表示初始状态是置位状态;

                                     //FALSE表示初始状态是复位状态

  __in_opt  LPCTSTR lpName //指定事件对象名称

);

当一个事件被创建后,程序就可以通过SetEventResetEvent函数来设置事件的状态:

BOOL WINAPI SetEvent(

  __in  HANDLE hEvent

);

BOOL WINAPI ResetEvent(

  __in  HANDLE hEvent

);

 

事件对象不仅可以用于一个进程中多线程同步,还可以用于多进程中的线程同步,只要在创建事件对象时指定事件名字,在其他进程中可以使用OpenEvent函数根据名称打开该事件对象进行使用:

HANDLE WINAPI OpenEvent(

  __in  DWORD dwDesiredAccess, //对事件对象的访问限制

  __in  BOOL bInheritHandle, //子进程是否继承该事件句柄

  __in  LPCTSTR lpName //事件对象的名字

);

 

 

实例代码如下:

#include <windows.h>

#include <stdio.h>

 

#define THREADCOUNT 4

 

HANDLE ghWriteEvent;

HANDLE ghThreads[THREADCOUNT];

 

DWORD WINAPI ThreadProc(LPVOID);

 

void CreateEventsAndThreads(void)

{

    int i;

    DWORD dwThreadID;

 

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

    // object to the nonsignaled state when it finishes writing to a

    // shared buffer.

 

    ghWriteEvent = CreateEvent(

        NULL,               // default security attributes

        TRUE,               // manual-reset event

        FALSE,              // initial state is nonsignaled

        TEXT("WriteEvent")  // object name

        );

 

    if (ghWriteEvent == NULL)

    {

        printf("CreateEvent failed (%d)/n", GetLastError());

        return;

    }

 

    // Create multiple threads to read from the buffer.

 

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

    {

        // TODO: More complex scenarios may require use of a parameter

        //   to the thread procedure, such as an event per thread to 

        //   be used for synchronization.

        ghThreads[i] = CreateThread(

            NULL,              // default security

            0,                 // default stack size

            ThreadProc,        // name of the thread function

            NULL,              // no thread parameters

            0,                 // default startup flags

            &dwThreadID);

 

        if (ghThreads[i] == NULL)

        {

            printf("CreateThread failed (%d)/n", GetLastError());

            return;

        }

    }

}

 

void WriteToBuffer(VOID)

{

    // TODO: Write to the shared buffer.

   

    printf("Main thread writing to the shared buffer.../n");

 

    // Set ghWriteEvent to signaled

 

    if (! SetEvent(ghWriteEvent) )

    {

        printf("SetEvent failed (%d)/n", GetLastError());

        return;

    }

}

 

void CloseEvents()

{

    // Close all event handles (currently, only one global handle).

   

    CloseHandle(ghWriteEvent);

}

 

void main()

{

    DWORD dwWaitResult;

 

    // TODO: Create the shared buffer

 

    // Create events and THREADCOUNT threads to read from the buffer

 

    CreateEventsAndThreads();

 

    // At this point, the reader threads have started and are most

    // likely waiting for the global event to be signaled. However,

    // it is safe to write to the buffer because the event is a

    // manual-reset event.

   

    WriteToBuffer();

 

    printf("Main thread waiting for threads to exit.../n");

 

    // The handle for each thread is signaled when the thread is

    // terminated.

    dwWaitResult = WaitForMultipleObjects(

        THREADCOUNT,   // number of handles in array

        ghThreads,     // array of thread handles

        TRUE,          // wait until all are signaled

        INFINITE);

 

    switch (dwWaitResult)

    {

        // All thread objects were signaled

        case WAIT_OBJECT_0:

            printf("All threads ended, cleaning up for application exit.../n");

            break;

 

        // An error occurred

        default:

            printf("WaitForMultipleObjects failed (%d)/n", GetLastError());

            return;

    }

           

    // Close the events to clean up

 

    CloseEvents();

}

 

DWORD WINAPI ThreadProc(LPVOID lpParam)

{

    DWORD dwWaitResult;

 

    printf("Thread %d waiting for write event.../n", GetCurrentThreadId());

   

    dwWaitResult = WaitForSingleObject(

        ghWriteEvent, // event handle

        INFINITE);    // indefinite wait

 

    switch (dwWaitResult)

    {

        // Event object was signaled

        case WAIT_OBJECT_0:

            //

            // TODO: Read from the shared buffer

            //

            printf("Thread %d reading from buffer/n",

                   GetCurrentThreadId());

            break;

 

        // An error occurred

        default:

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

            return 0;

    }

 

    // Now that we are done reading the buffer, we could use another

    // event to signal that this thread is no longer reading. This

    // example simply uses the thread handle for synchronization (the

    // handle is signaled when the thread terminates.)

 

    printf("Thread %d exiting/n", GetCurrentThreadId());

    return 1;

}

 

2)使用临界区对象进行线程间同步:

临界区对象(Critical Section)是定义在数据段中的一个CRITICAL_SECTION结构,它的维护和测试工作都是由Windows来完成的。结构应该定义成全局变量,因为在各线程中都有测试它。

定义了CRITICAL_SECTION结构后,必须首先对其进行初始化:

void WINAPI InitializeCriticalSection(

  __out  LPCRITICAL_SECTION lpCriticalSection

);

由于Windows系统保证临界区对象同时只能被一个线程进入,所以在需要独占操作的代码前加上进入临界区的操作,在代码后面加上离开临界区的操起,就可以保证操作的独占性了。

进入临界区的工作由函数EnterCriticalSection来完成:

void WINAPI EnterCriticalSection(

  __inout  LPCRITICAL_SECTION lpCriticalSection

);

如果当前其他线程拥有临界区,函数不会返回;反之如果函数返回就表示现在可以独占数据了。

当完成操作时,将临界区交还给Windows,以便其他线程可以申请使用,这个工作由函数LeaveCriticalSection来完成:

void WINAPI LeaveCriticalSection(

  __inout  LPCRITICAL_SECTION lpCriticalSection

);

 

最后,当程序不再需要临界区时,必须使用函数DeleteCriticalSection将它删除:

void WINAPI DeleteCriticalSection(

  __inout  LPCRITICAL_SECTION lpCriticalSection

);

 

与事件对象不同,由于临界区对象无法命名,所以无法跨进程使用,但正是因为事件对象可以跨进程使用,需要占用更多的资源,所以相比之下临界区对象在速度上的优势很明显。

使用临界区的缺点在于,如果某个线程进入临界区后挂掉了,那么将无法被其他等待的线程检测到,因为这些线程都被陷在EnterCriticalSection函数中了。而使用事件则不同,它可以在WaitForSingleObject函数中指定一个超时时间。

一般在对速度要求比较高,且不必跨进程进行同步的情况下,建议使用临界区对象。

 

实例代码片段如下:

// Global variable

CRITICAL_SECTION CriticalSection;

 

void main()

{

    ...

 

    // Initialize the critical section one time only.

    if (!InitializeCriticalSectionAndSpinCount(&CriticalSection,

        0x80000400) )

        return;

    ...

 

    // Release resources used by the critical section object.

    DeleteCriticalSection(&CriticalSection);

}

 

DWORD WINAPI ThreadProc( LPVOID lpParameter )

{

    ...

 

    // Request ownership of the critical section.

    EnterCriticalSection(&CriticalSection);

 

    // Access the shared resource.

 

    // Release ownership of the critical section.

    LeaveCriticalSection(&CriticalSection);

 

    ...

}

 

3)使用互斥量对象进行线程间同步:

和临界区类似,互斥量也是一种只允许一个线程获取的对象,但它是可以命名的,因而是可以跨越进程使用的。

互斥量使用CreateMutex函数创建:

HANDLE WINAPI CreateMutex(

  __in_opt  LPSECURITY_ATTRIBUTES lpMutexAttributes, //安全描述符

  __in      BOOL bInitialOwner, //TRUE,表示创建它的线程直接获取了该互斥量;

                                     // FALSE,表示互斥量创建后处于空闲状态

  __in_opt  LPCTSTR lpName //指定互斥对象的名字,可以使用OpenMutex来打开

);

 

释放互斥量使用函数ReleaseMutex,但是首先调用该函数的线程必须拥有该互斥量,这一点和使用Event不同,在任何线程中都可以使用SetEvent函数来将Event对象置位:

BOOL WINAPI ReleaseMutex(

  __in  HANDLE hMutex

);

 

互斥量是靠WaitForSingleObject函数来获取的,当互斥量被某个线程获取(即等待成功)后,它的状态是复位的,否则是置位的。

当不再使用互斥量时,可以使用CloseHandle来关闭它。

使用互斥量进行同步的缺点和使用事件对象类似,也是在效率上远远低于使用临界区;优点也是类似的,就是可以跨进程使用,可以检测到其他线程是否挂掉。

 

实例代码如下:

#include <windows.h>

#include <stdio.h>

 

#define THREADCOUNT 2

 

HANDLE ghMutex;

 

DWORD WINAPI WriteToDatabase( LPVOID );

 

void main()

{

    HANDLE aThread[THREADCOUNT];

    DWORD ThreadID;

    int i;

 

    // Create a mutex with no initial owner

 

    ghMutex = CreateMutex(

        NULL,              // default security attributes

        FALSE,             // initially not owned

        NULL);             // unnamed mutex

 

    if (ghMutex == NULL)

    {

        printf("CreateMutex error: %d/n", GetLastError());

        return;

    }

 

    // Create worker threads

 

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

    {

        aThread[i] = CreateThread(

                     NULL,       // default security attributes

                     0,          // default stack size

                     (LPTHREAD_START_ROUTINE) WriteToDatabase,

                     NULL,       // no thread function arguments

                     0,          // default creation flags

                     &ThreadID); // receive thread identifier

 

        if( aThread[i] == NULL )

        {

            printf("CreateThread error: %d/n", GetLastError());

            return;

        }

    }

 

    // Wait for all threads to terminate

 

    WaitForMultipleObjects(THREADCOUNT, aThread, TRUE, INFINITE);

 

    // Close thread and mutex handles

 

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

        CloseHandle(aThread[i]);

 

    CloseHandle(ghMutex);

}

 

DWORD WINAPI WriteToDatabase( LPVOID lpParam )

{

    DWORD dwCount=0, dwWaitResult;

 

    // Request ownership of mutex.

 

    while( dwCount < 20 )

    {

        dwWaitResult = WaitForSingleObject(

            ghMutex,    // handle to mutex

            INFINITE);  // no time-out interval

 

        switch (dwWaitResult)

        {

            // The thread got ownership of the mutex

            case WAIT_OBJECT_0:

                __try {

                    // TODO: Write to the database

                    printf("Thread %d writing to database.../n",

                            GetCurrentThreadId());

                    dwCount++;

                }

 

                __finally {

                    // Release ownership of the mutex object

                    if (! ReleaseMutex(ghMutex))

                    {

                        // Handle error.

                    }

                }

                break;

 

            // The thread got ownership of an abandoned mutex

            // The database is in an indeterminate state

            case WAIT_ABANDONED:

                return FALSE;

        }

    }

    return TRUE;

}

 

4)使用信号灯对象进行线程间同步:

信号灯对象是一个允许指定数量的线程获取的对象。信号灯对象一般是用于线程排队,考虑这样一种情况:服务器程序设置了3个工作线程以及n个和客户端连接的服务线程,服务线程需要在工作线程空闲时与之通讯,但3个工作线程都很忙的时候,就必须等待。这种情况下设置一个计数值为3的信号灯对象,服务线程和工作线程通讯前会首先尝试获取信号灯,这样就可以保证同时只有3个服务线程可以通过,而其他服务线程处于等待状态。

当信号灯对象的计数值为1时,就相当于一个互斥对象。

 

创建信号灯对象可以使用函数CreateSemaphore

HANDLE WINAPI CreateSemaphore(

  __in_opt  LPSECURITY_ATTRIBUTES lpSemaphoreAttributes, //安全描述符

  __in      LONG lInitialCount, //初始时计数值

  __in      LONG lMaximumCount, //对象的最大计数值,即最多允许多少个线程获取该对象

  __in_opt  LPCTSTR lpName //指定对象的名字

);

 

释放信号灯对象使用函数ReleaseSemaphore

BOOL WINAPI ReleaseSemaphore(

  __in       HANDLE hSemaphore, //

  __in       LONG lReleaseCount, //指定释放时将计数值增加多少

  __out_opt  LPLONG lpPreviousCount //函数在此返回释放前的计数值,不需要的话可设为NULL

);

当不再需要信号灯对象时,可以调用CloseHandle函数将它关闭。

 

信号灯对象是靠WaitForSingleObject函数来获取的,当对象被某个线程获取后,它的计数值减1,当计数值未减到0时,对象的状态是置位的,这意味着还有线程可以继续获取该对象;当计数值减到0时,对象的状态被复位。

 

实例代码如下:

#include <windows.h>

#include <stdio.h>

 

#define MAX_SEM_COUNT 10

#define THREADCOUNT 12

 

HANDLE ghSemaphore;

 

DWORD WINAPI ThreadProc( LPVOID );

 

void main()

{

    HANDLE aThread[THREADCOUNT];

    DWORD ThreadID;

    int i;

 

    // Create a semaphore with initial and max counts of MAX_SEM_COUNT

 

    ghSemaphore = CreateSemaphore(

        NULL,           // default security attributes

        MAX_SEM_COUNT,  // initial count

        MAX_SEM_COUNT,  // maximum count

        NULL);          // unnamed semaphore

 

    if (ghSemaphore == NULL)

    {

        printf("CreateSemaphore error: %d/n", GetLastError());

        return;

    }

 

    // Create worker threads

 

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

    {

        aThread[i] = CreateThread(

                     NULL,       // default security attributes

                     0,          // default stack size

                     (LPTHREAD_START_ROUTINE) ThreadProc,

                     NULL,       // no thread function arguments

                     0,          // default creation flags

                     &ThreadID); // receive thread identifier

 

        if( aThread[i] == NULL )

        {

            printf("CreateThread error: %d/n", GetLastError());

            return;

        }

    }

 

    // Wait for all threads to terminate

 

    WaitForMultipleObjects(THREADCOUNT, aThread, TRUE, INFINITE);

 

    // Close thread and semaphore handles

 

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

        CloseHandle(aThread[i]);

 

    CloseHandle(ghSemaphore);

}

 

DWORD WINAPI ThreadProc( LPVOID lpParam )

{

    DWORD dwWaitResult;

    BOOL bContinue=TRUE;

 

    while(bContinue)

    {

        // Try to enter the semaphore gate.

 

        dwWaitResult = WaitForSingleObject(

            ghSemaphore,   // handle to semaphore

            0L);           // zero-second time-out interval

 

        switch (dwWaitResult)

        {

            // The semaphore object was signaled.

            case WAIT_OBJECT_0:

                // TODO: Perform task

                printf("Thread %d: wait succeeded/n", GetCurrentThreadId());

                bContinue=FALSE;           

 

                // Simulate thread spending time on task

                Sleep(5);

 

                // Release the semaphore when task is finished

 

                if (!ReleaseSemaphore(

                        ghSemaphore,  // handle to semaphore

                        1,            // increase count by one

                        NULL) )       // not interested in previous count

                {

                    printf("ReleaseSemaphore error: %d/n", GetLastError());

                }

                break;

 

            // The semaphore was nonsignaled, so a time-out occurred.

            case WAIT_TIMEOUT:

                printf("Thread %d: wait timed out/n", GetCurrentThreadId());

                break;

        }

    }

    return TRUE;

}

 

 

 

 

你可能感兴趣的:(thread,编程,windows,Semaphore,attributes,winapi)