zoukankan      html  css  js  c++  java
  • 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 = "
    STOP
    ";
    
    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.
    ");
                SetEvent(mEventHandle_fetchable);
            }
            else
            {
                strcpy_s(mSharedBuffer, sizeof(mSharedBuffer), stop_keyword);
                ResetEvent(mEventHandle_fillable);
                printf("Fill finish. We can have a rest.
    ");
                SetEvent(mEventHandle_fetchable);
                break;
            }
            
            i++;
        }
    
        printf("fill_thread exit with code 0, last error=%d
    ", 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("
    Got. We must have a rest!
    ");
                break;
            }
            else
            {
                printf("fetched "%s".
    ", mSharedBuffer);
                mSharedBuffer[0] = 0;
                ResetEvent(mEventHandle_fetchable);
                Sleep(3000);
                printf("Please fill.
    
    ");
                SetEvent(mEventHandle_fillable);
            }
        }
         
        printf("fetch_thread exit with code 0, last error=%d
    ", 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("
    
    ***
    press ENTER key to exit!
    ");
        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 = "
    STOP
    ";
    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.
    ");
                SetEvent(mEventHandle_fetchable);
            }
            else
            {
                strcpy_s(mSharedBuffer, sizeof(mSharedBuffer), stop_keyword);
                ResetEvent(mEventHandle_fillable);
                printf("Fill finish. We can have a rest.
    ");
                SetEvent(mEventHandle_fetchable);
                break;
            }
    
            i++;
        }
    
        printf("fill_thread exit with code 0, last error=%d
    ", 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("
    Got. We must have a rest!
    ");
                break;
            }
            else
            {
                printf("fetched "%s".
    ", mSharedBuffer);
                mSharedBuffer[0] = 0;
                ResetEvent(mEventHandle_fetchable);
                Sleep(3000);
                printf("Please fill.
    
    ");
                SetEvent(mEventHandle_fillable);
            }
        }
    
        printf("fetch_thread exit with code 0, last error=%d
    ", 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
    ", mid, t);
                break;
            case WAIT_OBJECT_0:
                printf("onlooker %d, got fetch-able event
    ", mid);
                ResetEvent(mEventHandle_fetchable);
                break;
            }
    
            /* 比极限次数多一点,用于与 超时 做对比感受 */
            if (t > (testLimitedCount + 1))
            {
                stop = TRUE;
                printf("
    I must go home!
    ");
            }
        }
    
        printf("onlooker_thread (onlooker %d) go home with code 0.
    
    ", 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

    如果转载,请注明出处。https://www.cnblogs.com/ssdq/
  • 相关阅读:
    商业软件太贵?找开源替代品
    Odoo9发行说明
    Odoo(OpenERP)配置文件openerp-server.conf详解
    MyBatis-Generator最佳实践
    elasticsearch 口水篇(1) 安装、插件
    log4j直接输出日志到flume
    Maven编译时跳过Test
    Flume1.5.0的安装、部署、简单应用(含伪分布式、与hadoop2.2.0、hbase0.96的案例)
    Flume 1.5.0简单部署试用
    一共81个,开源大数据处理工具汇总(下),包括日志收集系统/集群管理/RPC等
  • 原文地址:https://www.cnblogs.com/ssdq/p/13157077.html
Copyright © 2011-2022 走看看