前言
Linux 中没有 Windows 系统中的 CreateEvent()、WaitEvent()、SetEvent()、ResetEvent() 等函数,本文将介绍如何使用 pevents 替代 Linux 缺失的函数。
pevents 介绍 pevents 的简介 pevents 是一个跨平台的轻量级 C++ 库,旨在为 POSIX 系统提供 WIN32 事件的实现。pevents 提供了 Windows 平台手动和自动重置事件的大部分功能,最显著的是支持同时等待多个事件(WaitForMultipleObjects),而且支持 Windows、FreeBSD、Linux、macOS、iOS、Android 等平台。
pevents 的 API API 函数 pevents 的 API 是根据 Windows 的 CreateEvent()、WaitEvent() 和 WaitForMultipleObjects() 函数编写的,熟悉 WIN32 事件的开发人员应该可以将代码库切换到 pevents API。虚假唤醒是 Linux 下系统编程的正常部分,也是来自 Windows 世界的开发人员的常见陷阱,pevents 可以保证不存在虚假唤醒和等待返回的数据的正确性,其提供了如下的 API:
1 2 3 4 5 6 7 8 9 10 int SetEvent (neosmart_event_t event) ;int ResetEvent (neosmart_event_t event) ;int PulseEvent (neosmart_event_t event) ;int DestroyEvent (neosmart_event_t event) ;neosmart_event_t CreateEvent (bool manualReset, bool initialState) ;int WaitForEvent (neosmart_event_t event, uint64_t milliseconds) ;int WaitForMultipleEvents (neosmart_event_t *events, int count, bool waitAll, uint64_t milliseconds) ;int WaitForMultipleEvents (neosmart_event_t *events, int count, bool waitAll, uint64_t milliseconds, int &index) ;
事件状态的类型 1 2 3 4 5 6 7 neosmart_event_t CreateEvent( // true:表示手动,在 WaitEvent 后需要手动调用 ResetEvent 清除事件信号。false:表示自动,在 WaitEvent 后,系统会自动清除事件信号 bool manualReset, // 初始状态,false 为无信号,true 为有信号 bool initialState );
1 2 3 4 5 6 int WaitForEvent( // 句柄对象 neosmart_event_t event, // 等待的时间(毫秒) uint64_t milliseconds );
事件状态的类型WAIT_TIMEOUT:等待超时WAIT_OBJECT_0:句柄对象处于有信号状态WAIT_FAILED:出现错误,可通过 GetLastError() 函数得到错误码WAIT_ABANDONED:说明句柄代表的对象是个互斥对象,并且正在被其它线程占用 注意
在 Linux 平台,pevents 的事件状态只支持使用 WAIT_TIMEOUT,且有信号的时候 WaitEvent() 函数的返回值是 0,而在 Windows 平台则支持上述四种事件状态
pevents 的项目结构 核心代码在 src/ 目录 单元测试代码(通过 Meson 构建)在 test/ 目录 在 examples/ 目录中可以找到演示 pevents 用法的跨平台应用示例程序 pevents 的编译构建 pevents 使用的构建工具是 Meson,目前这仅用于支持 pevents 核心代码及其单元测试的自动化构建 / 测试。值得一提的是,开发人员不需要担心构建工具的差异性,pevents 是特意基于 C/C++ 标准编写的,避免了复杂的配置或依赖于平台的构建指令的需要。
pevents 的编译参数 通过编译参数 -DWFMO 与 -DPULSE,可以在编译时让 pevents 启用不同的功能:
WFMO:启用 WFMO 功能,如果需要使用 WaitForMultipleEvents() 函数,建议仅使用 WFMO 进行编译,因为它会为所有事件对象增加开销(较小)。PULSE:启用 PulseEvent 功能,PulseEvent() 在 Windows 平台从根本上被破坏了,一般不应该被使用,当你调用它时,它几乎永远不会做你认为你正在做的事情。pevents 包含这个函数只是为了让现有的(有缺陷的)代码从 WIN32 移植到 Unix/Linux 平台更容易,并且这个函数默认没有编译到 pevents 中。Meson 指定编译参数 在 Meson 中,可以通过 meson_options.txt 配置文件指定编译参数,让 pevents 启用不同的功能
1 2 3 4 option('wfmo', type: 'boolean', value: true, description: 'Enable WFMO events') option('pulse', type: 'boolean', value: false, description: 'Enable PulseEvent() function')
CMake 指定编译参数 在 CMake 中,可以通过 CMakeLists.txt 配置文件指定编译参数,让 pevents 启用不同的功能
1 set(CMAKE_CXX_FLAGS "-std=c++11 -lpthread -DWFMO")
pevents 运行示例代码 提示
值得一提的是,pevents 的核心 C++ 源文件是 pevents.h、pevents.cpp
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 $ git clone git@github.com:clay-world/pevents.git$ cd pevents$ meson build$ cd build$ ninja $ ./sample
pevents 的实战案例 编译说明 下面给出的案例使用了 pthread,由于 pthread 不是 Linux 系统默认的库,因此链接时需要使用静态库 libpthread.a。简而言之,在使用 pthread_create() 创建线程,以及调用 pthread_atfork() 函数建立 fork 处理程序时,需要通过 -lpthread 参数链接该库,同时还需要在 C++ 源文件里添加头文件 pthread.h。
提示
为了可以正常编译使用了 pthread 的项目代码,不同构建工具的使用说明如下:
若使用 G++ 编译 C++ 项目,则编译命令的示例如下:
1 2 $ g ++ main.cpp -o main -lpthread
若使用 CMake 构建 C++ 项目,则 CMakeLists.txt 配置文件的示例内容如下:
1 2 3 set(CMAKE_CXX_FLAGS "-std=c++11 -lpthread -DWFMO") add_executable(main main.cpp)
实战案例一 CreateEvent(true, true) - 手动清除事件信号,初始状态为有信号,点击下载 基于 CMake 构建的完整案例代码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 #include <iostream> #include <unistd.h> #include <pthread.h> #include "pevents.h" using namespace std;using namespace neosmart;neosmart_event_t g_hEvent = NULL ;void printIds (const char *s) { pid_t pid = getpid (); pthread_t tid = pthread_self (); printf ("%s pid %u tid %u (0x%x)\n" , s, (unsigned int ) pid, (unsigned int ) tid, (unsigned int ) tid); } void *procFunc1 (void *args) { printIds ("thread-1" ); if (WaitForEvent (g_hEvent, 1 ) == 0 ) { cout << "thread-1 is working..." << endl; } return ((void *) 0 ); } void *procFunc2 (void *args) { printIds ("thread-2" ); if (WaitForEvent (g_hEvent, 1 ) == 0 ) { cout << "thread-2 is working..." << endl; } return ((void *) 0 ); } int main () { g_hEvent = CreateEvent (true , true ); pthread_t ntid1; pthread_create (&ntid1, NULL , procFunc1, NULL ); sleep (1 ); pthread_t ntid2; pthread_create (&ntid2, NULL , procFunc2, NULL ); sleep (5 ); }
程序运行的结果如下:
1 2 3 4 thread-1 pid 62705 tid 2336241408 (0x8b403700) thread-1 is working... thread-2 pid 62705 tid 2327848704 (0x8ac02700) thread-2 is working...
提示
可以看到线程 1 和线程 2 都完整执行了,这是因为创建的事件是需手动 Reset 才会变为无信号的,所以执行完线程 1 后事件仍处于有信号的状态,所以线程 2 的逻辑才会被继续执行。
实战案例二 CreateEvent(false, true) - 自动清除事件信号,且初始状态为有信号,点击下载 基于 CMake 构建的完整案例代码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 #include <iostream> #include <unistd.h> #include <pthread.h> #include "pevents.h" using namespace std;using namespace neosmart;neosmart_event_t g_hEvent = NULL ;void printIds (const char *s) { pid_t pid = getpid (); pthread_t tid = pthread_self (); printf ("%s pid %u tid %u (0x%x)\n" , s, (unsigned int ) pid, (unsigned int ) tid, (unsigned int ) tid); } void *procFunc1 (void *args) { printIds ("thread-1" ); if (WaitForEvent (g_hEvent, 1 ) == 0 ) { cout << "thread-1 is working..." << endl; } return ((void *) 0 ); } void *procFunc2 (void *args) { printIds ("thread-2" ); if (WaitForEvent (g_hEvent, 1 ) == 0 ) { cout << "thread-2 is working..." << endl; } return ((void *) 0 ); } int main () { g_hEvent = CreateEvent (false , true ); pthread_t ntid1; pthread_create (&ntid1, NULL , procFunc1, NULL ); sleep (1 ); pthread_t ntid2; pthread_create (&ntid2, NULL , procFunc2, NULL ); sleep (5 ); }
程序运行的结果如下:
1 2 3 thread-1 pid 59685 tid 2245932800 (0x85de3700) thread-1 is working... thread-2 pid 59685 tid 2237540096 (0x855e2700)
提示
可以看到只有线程 1 完整执行了,这是由于事件在执行完线程 1 后被系统自动重置为无信号,所以线程 2 中的逻辑没有被执行。
实战案例三 CreateEvent(true, false) - 手动清除事件信号,初始状态为无信号,包括 SetEvent() 与 ResetEvent() 的使用,点击下载 基于 CMake 构建的完整案例代码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 #include <iostream> #include <unistd.h> #include <pthread.h> #include "pevents.h" using namespace std;using namespace neosmart;neosmart_event_t g_hEvent = NULL ;void printIds (const char *s) { pid_t pid = getpid (); pthread_t tid = pthread_self (); printf ("%s pid %u tid %u (0x%x)\n" , s, (unsigned int ) pid, (unsigned int ) tid, (unsigned int ) tid); } void *procFunc1 (void *args) { printIds ("thread-1" ); if (WaitForEvent (g_hEvent, 1 ) == 0 ) { cout << "thread-1 is working..." << endl; } ResetEvent (g_hEvent); return ((void *) 0 ); } void *procFunc2 (void *args) { printIds ("thread-2" ); if (WaitForEvent (g_hEvent, 1 ) == 0 ) { cout << "thread-2 is working..." << endl; } return ((void *) 0 ); } void func1 () { g_hEvent = CreateEvent (true , true ); pthread_t ntid1; pthread_create (&ntid1, NULL , procFunc1, NULL ); sleep (1 ); pthread_t ntid2; pthread_create (&ntid2, NULL , procFunc2, NULL ); sleep (5 ); } int main () { g_hEvent = CreateEvent (true , false ); SetEvent (g_hEvent); pthread_t ntid1; pthread_create (&ntid1, NULL , procFunc1, NULL ); sleep (1 ); pthread_t ntid2; pthread_create (&ntid2, NULL , procFunc2, NULL ); sleep (5 ); return 0 ; }
程序运行的结果如下:
1 2 3 thread-1 pid 70368 tid 2745513728 (0xa3a53700) thread-1 is working... thread-2 pid 70368 tid 2737121024 (0xa3252700)
提示
可以看到只有线程 1 完整执行了,这是因为线程 1 在执行之前事件是有信号的,执行完成后事件被手动重置为无信号,所以线程 2 中的逻辑没有被执行。
参考资料