CreateEvent函数使用记录

最近在写一个串口对话框程序,使用到了CreateEvent函数。于是就特地谷歌了一番。

总体感觉就是CreateEvent相当于创建了一个信号灯,在A线程里面点灯(SetEvent),在B线程中观察等待(WaitForSingleObject),看到灯亮了就开始干活。

也就是说,当我们SetEvent时,就等于是告诉该event的观察者一个消息说:“刚才发生你正在关注的事情了,你可以按照你的想法去干活了”,可以看成是“事件回调”。

所以,CreateEvent在串口通讯中的读写串口操作中用到了,特别是读串口,我们不可能持续不断的轮询读取,最期望的方法是收到一个串口结束数据的中断,然后我们再去读取。

可是在系统平台上,硬件中断或者其他的一些外部事件的产生不是我们APP想去监听就能监听到的,系统层封装了这一切的元素,我们APP开发者只能借助系统提供API来间接达到监听的目的。

所以这个CreateEvent在我们监听串口收到数据的时候就显得非常有用了。

 

创建信号灯的时候:

  1. 可以设定信号灯是否人工(手动)关闭(CreateEvent的第二个参数),TRUE为手动关闭,FALSE为自动关闭。
  2. 可以设定信号灯的初始状态(CreateEvent的第三个参数)为TRUE或者FALSE。

所谓的人工关闭,就是程序员在合适的地方主动调用ResetEvent来关闭信号灯。

而信号灯的状态决定了WaitForSingleObject是否可以结束等待,如果为TRUE则结束等待,否则根据WaitForSingleObject的最后一个参数来决定等待多久。

 

 

对于一个线程,不可能即观察一个灯的状态,同时又自己来对这个灯进行点灯、灭灯操作,这样做没有任何意义。

 

总结信号灯的使用需要涉及如下五点:

  1. 创建信号灯。调用CreateEvent,要与CloseHandle成对使用。
  2. 创建观察信号灯的线程。
  3. 设置信号灯的状态。就是点灯了,把灯点亮,告诉需要观察这个灯状态的线程注意干活。
  4. 复位信号灯的状态。取决于创建信号灯的时候是否人工复位。
  5. 删除信号灯。调用CloseHandle来删除,等于是释放内存。

为此,写了如下一段测试代码。主要就是两个线程和两个信号灯:

  1. 填充线程。功能是观察fillable信号灯,当fillable灯亮之后,就灭fillable灯,然后往全局buffer里面填充数据,再点亮fetchable灯。
  2. 拉取线程。功能是观察fetchable信号灯,当fetchable灯亮之后,就灭fetchable灯,接着从全局buffer里面读取数据,最后再点亮fillable灯。

 

对于信号灯的初始化(CreateEvent函数的第三个参数),自然是fillable要初始化为亮灯状态,fetchable要初始化为灭灯状态。很自然地,没有数据你怎么去fetch嘛,所以必然要先fill,后fetch,这就是这两个信号灯的初值了。

 

对于主线程中的 WaitForMultipleObjects 函数,顾名思义,自然就是等待多个信号条件都满足的时候才会结束等待,这与我们在新开线程中使用的WaitForSingleObject算是一对兄弟了。所以主线程中创建两个子线程以后,不能自己直接结束了,应该要调用WaitForMultipleObjects来等待两个子线程的结束,然后删除信号灯,要不然可能内存泄露。

#include "stdafx.h"
#include <windows.h>
#include <stdio.h>
#include <string.h>

static HANDLE mEventHandle_fillable;
static HANDLE mEventHandle_fetchable;
static char mSharedBuffer[128] = {0};
static const char *stop_keyword = "\r\nSTOP\r\n";

static DWORD WINAPI fill_thread(LPVOID lpParam)
{
    int i = 0;

    while(WAIT_OBJECT_0 == WaitForSingleObject( 
        mEventHandle_fillable,
        INFINITE))
    {
        if (i < 3)
        {
            sprintf_s(mSharedBuffer, sizeof(mSharedBuffer), "%d", i);
            ResetEvent(mEventHandle_fillable);
            printf("Please fetch your data.\n");
            SetEvent(mEventHandle_fetchable);
        }
        else
        {
            strcpy_s(mSharedBuffer, sizeof(mSharedBuffer), stop_keyword);
            ResetEvent(mEventHandle_fillable);
            printf("Fill finish. We can have a rest.\n");
            SetEvent(mEventHandle_fetchable);
            break;
        }
        
        i++;
    }

    printf("fill_thread exit with code 0, last error=%d\n", GetLastError()); 
    return 0;
}

static DWORD WINAPI fetch_thread(LPVOID lpParam)
{
    while(WAIT_OBJECT_0 == WaitForSingleObject( 
        mEventHandle_fetchable,
        INFINITE))
    {
        if (0 == strcmp(mSharedBuffer, stop_keyword))
        {
            ResetEvent(mEventHandle_fetchable);
            printf("\nGot. We must have a rest!\n");
            break;
        }
        else
        {
            printf("fetched \"%s\".\n", mSharedBuffer);
            mSharedBuffer[0] = 0;
            ResetEvent(mEventHandle_fetchable);
            Sleep(3000);
            printf("Please fill.\n\n");
            SetEvent(mEventHandle_fillable);
        }
    }
     
    printf("fetch_thread exit with code 0, last error=%d\n", GetLastError()); 
    return 0;
}

extern void test_CreateEvent(void)
{
    HANDLE threads[2];
    DWORD tid;

    mEventHandle_fillable = CreateEvent(NULL, TRUE, TRUE, TEXT("fillable"));
    mEventHandle_fetchable = CreateEvent(NULL, TRUE, FALSE, TEXT("fetchable"));

    threads[0] = CreateThread(NULL, 0, fill_thread, NULL, 0, &tid);
    threads[1] = CreateThread(NULL, 0, fetch_thread, NULL, 0, &tid);

    WaitForMultipleObjects(2, threads, TRUE, INFINITE);

    CloseHandle(threads[0]);
    CloseHandle(threads[1]);

    CloseHandle(mEventHandle_fillable);
    CloseHandle(mEventHandle_fetchable);
}

int _tmain(int argc, _TCHAR* argv[])
{
    test_CreateEvent();
    printf("\n\n***\npress ENTER key to exit!\n");
    getchar();
    return 0;
}

测试结果截图

 

新修改的代码,加入了5个旁观者线程来对感受。

#include "stdafx.h"
#include <windows.h>
#include <stdio.h>
#include <string.h>

static HANDLE mEventHandle_fillable;
static HANDLE mEventHandle_fetchable;
static char mSharedBuffer[128] = {0};
static const char *stop_keyword = "\r\nSTOP\r\n";
static const int testLimitedCount = 3;

static DWORD WINAPI fill_thread(LPVOID lpParam)
{
    int i = 0;

    while(WAIT_OBJECT_0 == WaitForSingleObject( 
        mEventHandle_fillable,
        INFINITE))
    {
        if (i < testLimitedCount)
        {
            sprintf_s(mSharedBuffer, sizeof(mSharedBuffer), "%d", i);
            ResetEvent(mEventHandle_fillable);
            printf("Please fetch your data.\n");
            SetEvent(mEventHandle_fetchable);
        }
        else
        {
            strcpy_s(mSharedBuffer, sizeof(mSharedBuffer), stop_keyword);
            ResetEvent(mEventHandle_fillable);
            printf("Fill finish. We can have a rest.\n");
            SetEvent(mEventHandle_fetchable);
            break;
        }

        i++;
    }

    printf("fill_thread exit with code 0, last error=%d\n", GetLastError()); 
    return 0;
}

static DWORD WINAPI fetch_thread(LPVOID lpParam)
{
    while(WAIT_OBJECT_0 == WaitForSingleObject( 
        mEventHandle_fetchable,
        INFINITE))
    {
        if (0 == strcmp(mSharedBuffer, stop_keyword))
        {
            ResetEvent(mEventHandle_fetchable);
            printf("\nGot. We must have a rest!\n");
            break;
        }
        else
        {
            printf("fetched \"%s\".\n", mSharedBuffer);
            mSharedBuffer[0] = 0;
            ResetEvent(mEventHandle_fetchable);
            Sleep(3000);
            printf("Please fill.\n\n");
            SetEvent(mEventHandle_fillable);
        }
    }

    printf("fetch_thread exit with code 0, last error=%d\n", GetLastError()); 
    return 0;
}

/* 旁观者线程 */
static DWORD WINAPI onlooker_thread(LPVOID param)
{
    DWORD retval;
    int t = 0;
    BOOL stop = FALSE;
    int mid = (int)param;

    while(!stop)
    {
        /* 本来是INFINITE,但是fill/fetch线程结束后导致这里死等,就弄了一个极限等待时间。
        如果fetch-able event发出的时候,这里刚好超时推出去了,当它再次 wai t的时候,就只能等待下一次的 event 了,
        因为 even t每次都会被 reset 掉。 */
        retval = WaitForSingleObject( 
            mEventHandle_fetchable,
            10*1000);
        t ++;
        switch (retval)
        {
        case WAIT_TIMEOUT:
            printf("onlooker %d wait timeout, time=%d\n", mid, t);
            break;
        case WAIT_OBJECT_0:
            printf("onlooker %d, got fetch-able event\n", mid);
            ResetEvent(mEventHandle_fetchable);
            break;
        }

        /* 比极限次数多一点,用于与 超时 做对比感受 */
        if (t > (testLimitedCount + 1))
        {
            stop = TRUE;
            printf("\nI must go home!\n");
        }
    }

    printf("onlooker_thread (onlooker %d) go home with code 0.\n\n", mid);
    return 0;
}

#define ONLOOKER_TOTAL 5
extern void test_CreateEvent(void)
{
    HANDLE threads[2 + ONLOOKER_TOTAL];
    DWORD tid;

    /* 这里本打算将fill的初始值设定为TRUE的,
    但是线程增加之后,可能造成有些线程还没启动就发出了event,
    所以我就改成了FALSE,然后手动设置。*/
    mEventHandle_fillable = CreateEvent(NULL, TRUE, /*TRUE*/FALSE, TEXT("fillable"));
    mEventHandle_fetchable = CreateEvent(NULL, TRUE, FALSE, TEXT("fetchable"));

    threads[0] = CreateThread(NULL, 0, fill_thread, NULL, 0, &tid);
    threads[1] = CreateThread(NULL, 0, fetch_thread, NULL, 0, &tid);
    for (int i=0; i<ONLOOKER_TOTAL; i++)
    {
        threads[2 + i] = CreateThread(NULL, 0, onlooker_thread, (LPVOID)i, 0, &tid);
    }

    /* 这里等待3秒钟,然新建立的线程有足够的时间去启动起来。然后通知 fill 线程去填充数据 */
    Sleep(3000);
    SetEvent(mEventHandle_fillable);

    WaitForMultipleObjects(2 + ONLOOKER_TOTAL, threads, TRUE, INFINITE);

    CloseHandle(threads[0]);
    CloseHandle(threads[1]);
    for (int i=0; i<ONLOOKER_TOTAL; i++)
    {
        CloseHandle(threads[2 + i]);
    }

    CloseHandle(mEventHandle_fillable);
    CloseHandle(mEventHandle_fetchable);
}

测试截图

 

结论:

无论有多少个线程在等待event的产生,当event被set之后,即便先执行的线程里面执行了ResetEvent操作,其他后面的线程依然会被执行一遍。

 

 

 

 

代码逻辑图

 

参考:https://docs.microsoft.com/en-us/windows/win32/sync/using-event-objects

posted @ 2020-06-18 13:32  -ssdq-  阅读(814)  评论(0编辑  收藏  举报