前言
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 中的逻辑没有被执行。
参考资料