多线程同步-事件内核对象

1.简介

事件最通常的用途是,让一个线程执行初始化工作,然后再触发另一个线程,让它执行剩下的工作。一开始我们将事件初始化为未触发的状态,然后当线程完成初始化工作的时候,触发事件。此时,另一个线程一直在等待该事件,它发现事件被触发,于是变成可调度状态。第二个线程知道第一个线程已经完成了它的工作。

事件内核对象有三部分构成:

  • 一个使用计数
  • 一个用于指明该事件是个自动重置的事件还是一个人工重置的事件的布尔值
  • 一个用来表示事件有没有被触发的布尔值

当人工重置的事件得到通知时,等待该事件的所有线程均变为可调度线程。

当自动重置的事件得到通知时,等待该事件的线程中只有一个线程变为可调度线程。

用CreateEvent函数创建事件内核对象。

HANDLE CreateEvent
(
   PSECURITY_ATTRIBUTES psa,
   BOOL fManualReset,
   BOOL fInitialState,
   PCTSTR pszName
);
  • psa:指向SECURITY_ATTRIBUTES结构的指针。
  • fManualReset:告诉系统是创建一个人工重置的事件(TRUE)还是创建一个自动重置的事件(FALSE)。
  • fInitialState:指明该事件是要初始化为已通知状态(TRUE)还是未通知状态(FALSE)。
  • pszName:事件对象的名称。

一旦事件已经创建,就可以直接控制它的状态。

//事件改为已通知状态
BOOL SetEvent(HANDLE hEvent);

//事件改为未通知状态
BOOL ResetEvent(HANDLE hEvent);

2.示例

// Event.cpp : 此文件包含 "main" 函数。程序执行将在此处开始并结束。
//

#include <iostream>
#include <Windows.h>

#define THREADCOUNT 4 

HANDLE ghWriteEvent;
HANDLE ghThreads[THREADCOUNT];

DWORD WINAPI ThreadProc(LPVOID);

void CreateEventsAndThreads(void)
{
    int i;
    DWORD dwThreadID;

    //创建一个手动重置事件对象
    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;
    }

    // 创建多线程
    for (i = 0; i < THREADCOUNT; i++)
    {
        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);
}

int main(void)
{
    DWORD dwWaitResult;

    //创建事件和多线程
    CreateEventsAndThreads();

    //调用SetEvent,将事件变为触发状态
    WriteToBuffer();

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

    // 当线程被调用时,每个线程的句柄都会发出信号,接着往下走
    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 1;
    }

    // Close the events to clean up

    CloseEvents();

    return 0;
}

DWORD WINAPI ThreadProc(LPVOID lpParam)
{
    // lpParam not used in this example.
    UNREFERENCED_PARAMETER(lpParam);

    DWORD dwWaitResult;

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

    //等待ghWriteEvent变为触发状态
    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;
    }

    printf("Thread %d exiting\n", GetCurrentThreadId());
    return 1;
}

运行结果

如果这里使用自动重置的事件而不是人工重置的事件,那么应用程序的行为特性就有很大的差别。当主线程调用SetEvent之后,系统只允许一个辅助线程变成可调度状态。同样,也无法保证系统将使哪个线程变为可调度状态。其余线程将继续等待。

猜你喜欢

转载自blog.csdn.net/wzz953200463/article/details/127270901