Linux和Windows系统中处理“事件”对象的函数

描述

在 Windows 代码库中,有一个常量INFINITE,它由第二个参数传递。此常量指示线程无限期地等待事件。常量在WinBase.h中声明,定义为0xFFFFFFFF(或 -1)。

此外,Windows代码还包括WAIT_TIMEOUT。此条件在 Linux 中没有表示。实际上,借助以下功能可以绕过此限制:

int pthread_tryjoin_np(pthread_t thread, void **retval) ;

int pthread_timedjoin_np(

pthread_t thread, 

void **retval, 

const struct timespec *abstime

);

如果您参考pthread_tryjoin_np帮助页面,您可以看到EBUSY可能是一个错误,并且 WaitForSingleObject无法通知我们。要了解线程的状态并识别其退出代码,必须调用该函数:

BOOL GetExitCodeThread(HANDLE hThread, PDWORD pdwExitCode);

退出代码作为pdwExitCode指向的变量返回。如果在调用函数时线程尚未终止,则STILL_ACTIVE标识符将填充为变量。如果调用成功,则函数返回TRUE。

让我们考虑一个 Linux 的pthread_tryjoin_np函数用法和Windows 的 GetExitCodeThreadWaitForSingleObject函数的情况。

#ifdef __PL_WINDOWS__

DWORD dwret;

BOOL bret;

DWORD h_process_command_thread_exit_code;

if (h_process_command_thread != NULL) {

bret = GetExitCodeThread(

h_process_command_thread,

&h_process_command_thread_exit_code

);

if (h_process_command_thread_exit_code == STILL_ACTIVE) {

dwret = WaitForSingleObject(

h_process_command_thread,

5000  // 5000ms

);

switch (dwret) {

case WAIT_OBJECT_0:

// everything from this point on is good break;

case WAIT_TIMEOUT:

case WAIT_FAILED:

default:

SetLastError(dwret);

break;

}

}

}

#endif //__PL_WINDOWS__

#ifdef __PL_LINUX__

int iret;

struct timespec wait_time = { 0 };

if (h_process_command_thread_initialized == 1) {

iret = pthread_tryjoin_np(

h_process_command_thread,

NULL

);

if ((iret != 0) && (iret != EBUSY)) {

//TODO: process the error

}

if (iret == EBUSY) {

clock_gettime(CLOCK_REALTIME, &wait_time);

ADD_MS_TO_TIMESPEC(wait_time, 5000);

iret = pthread_timedjoin_np(

h_process_command_thread,

NULL,

&wait_time

);

switch (iret) {

case 0:

// everything from this point on is good

break;

case ETIMEDOUT:

case EINVAL:

default:

break;

}

}

}

#endif //__PL_LINUX__

细心的读者会注意到ADD_MS_TO_TIMESPEC是 Linux 操作系统中未表示的宏。 宏被添加到wait_time5000毫秒,但宏实现不在本文的讨论范围之内。还应该提到的是,在 Linux 中我们需要引入一个单独的变量 h_process_command_thread_initialized,因为pthread_t是无符号的 long(一般来说),我们无法验证它。

让我们总结一下结果。Linux 和 Windows 操作系统提供了在应用程序内部创建线程的机会。在Windows操作系统中,类型是HANDLE和Linux-pthread_t。如果在 Linux 操作系统中创建可连接线程,即使我们确定线程已终止,也有必要编写pthread_join。这种做法将帮助我们避免系统资源泄漏。

讨论的功能记录在表 1 中。

 

Linux函数 窗口函数
pthread_create 开始线程
pthread_join WaitForSingleObject(.., INFINITE)
pthread_timedjoin_np GetExitCodeThreadWaitForSingleObject
pthread_tryjoin_np 获取退出代码线程

 

表 1.Windows 和 Linux 操作系统中的线程同步函数。

事件

事件是内核对象变体的实例。事件通知操作终止,通常在线程执行初始化,然后向另一个线程发出可以继续工作的信号时使用。初始化线程将 «event» 对象转换为无信号状态,然后继续其操作。完成后,它将事件释放到信号状态。反过来,一直在等待事件将其状态更改为信号的另一个线程恢复并再次成为计划线程。

让我们来看看在Windows和Linux操作系统中处理“事件”对象的函数。

在Windows操作系统中,使用CreateEvent函数创建一个“事件”对象:

HANDLE CreateEvent(

PSECURITY_ATTRIBUTES psa,

BOOL fManualReset,

BOOL fInitialState,

PCSTR pszName

);

让我们更清楚地关注fManualReset和fInitialState参数。BOOL类型的 FManualReset参数通知系统需要创建手动重置事件 (TRUE) 或自动重置事件 (FALSE)。fInitialState参数确定事件的初始状态:已发出信号 (TRUE) 或未发出信号 (FALSE)。

创建事件后,可以管理状态。要将事件转换为信号状态,您需要调用:

Bool SetEvent(HANDLE hEvent);

若要将事件状态更改为无信号,需要调用:

Bool ResetEvent(HANDLE hEvent);

要等待事件信号,您需要使用我们已经熟悉的WaitForSingleObject函数。

在Linux操作系统中,“事件”对象表示整数描述符。一个整数 «event» 对象是使用eventfd函数创建的:

int eventfd(unsigned int initval, int flags);

initval参数是一个内核服务计数器。flags参数是eventfd行为修改所必需的,可以是EFD_CLOEXEC、EFD_NONBLOCK 或EFD_SEMAPHORE。如果成功终止,eventfd将返回一个新的文件描述符,该描述符可用于链接eventfd对象。

与SetEvent 类似,我们可以使用eventfd_write调用:

ssize_t eventfd_write(int fd, const void *buf, size_t count);

从缓冲区调用写入时,会将 8 字节整数值添加到计数器中。最大计数器值可以是 64 位无符号减 1。如果函数调用成功,则返回写入的字节数。

在我们讨论ResetEvent类似物之前,让我们看一下轮询函数。

#include

int poll(struct pollfd fdarray[], nfds_t nfds, int timeout);

轮询功能允许应用程序同时阻止多个描述符,并在其中任何一个准备好读取或写入时立即接收通知。民意调查工作(一般)可以描述如下:

当任何描述符准备好进行输入-输出操作时发出通知。

如果没有任何描述符准备就绪,请进入睡眠模式,直到一个或多个描述符准备就绪。

如果有可用的描述符准备用于输入-输出,请处理它们而不会阻塞。

返回到步骤 1。

Linux 操作系统为多路复用输入输出提供了三个实体:用于选择(选择)、轮询(轮询)、扩展轮询(epoll)的接口。

那些有使用select经验的人可能会欣赏投票的优势,它使用更有效的方法,基于位掩码使用三组描述符。轮询调用适用于文件描述符指向的单个nfdspollfd结构数组。

让我们看一下pollfd结构定义:

struct pollfd {

int fd; /* file descriptor */

short events; /* requested events */

short revents; /* returned events */

};

每个pollfd结构中都指示了一个将被跟踪的文件描述符。可以将多个文件描述符传递给轮询函数(结构的 pollfd数组)。fdarray数组中的元素数由nfds参数确定。

为了将我们感兴趣的事件传达给内核,有必要在数组中每个元素的事件字段中写入表 2 中的一个或多个值。从轮询函数返回后,内核指定每个描述符发生的事件。

 

名字 事件 活动 描述
波林 + + 数据可供读取(高优先级除外)
波尔德规范 + + 常规数据(优先级 0)可供读取
波尔德班德 + + 具有非零优先级的数据可供读取
波普里普里 + + 高优先级数据可供读取
波罗特 + + 数据可供写入
波尔沃诺姆 + + 类似于 波劳特
民意调查带 + + 具有非零优先级的数据可用于写入
波勒尔   + 发生错误
波尔赫普   + 连接丢失
波伦瓦尔   + 描述符和打开的文件不匹配

 

表 2.事件和轮询函数的revents标志的可能值。

参数超时定义发生指定事件的等待时间。超时有三种可能的值。

timeout= -1:等待时间是无限的(在 WaitForSingleObject 中为 INFINITE)。

timeout= 0:等待时间等于 0,表示需要检查所有指定的描述符并将控制权交还给调用程序。

超时> 0:等待时间不超过超时毫秒。

在查看了轮询函数之后,我们可以得出结论,在Windows操作系统中,“事件”对象与WaitForSingleObject类似。

让我们转到 Linux 的ResetEvent类似物。

#ifdef __PL_LINUX__

struct pollfd wait_object;

uint64_t event_value;

int ret;

if (eventfd_descriptor > 0) { // Descriptor created by eventfd(0,0)          

wait_object.fd = eventfd_descriptor;

wait_object.events = POLLIN;

wait_object.revents = 0;

ret = poll(&wait_object, 1, 0); // Do not wait

if (ret < 0) { // Error

} else {

if ((wait_object.revents & POLLIN) != 0) {

iret = eventfd_read(eventfd_descriptor, &event_value);

if (iret != 0) { // Error }

}

}

}

#endif //__PL_LINUX__

最初我们检查eventfd_descriptor是否大于零[2](实际上,这最初是由eventfd函数创建的,没有错误)。之后,我们初始化pollfd函数并运行轮询。需要执行轮询以检查是否有可用的数据可供读取。如果有此类数据,我们将读取它。

通过上述所有内容的镜头,让我们反映表3中的结果:

 

窗口函数 Linux函数
创建事件 事件FD
设置事件 eventfd_write
重置事件 投票/eventfd_read
等待单个对象 民意调查

 

表 3.用于处理 Windows 中的事件及其在 Linux 中的类似事件的主要函数。

审核编辑:郭婷

打开APP阅读更多精彩内容
声明:本文内容及配图由入驻作者撰写或者入驻合作网站授权转载。文章观点仅代表作者本人,不代表电子发烧友网立场。文章及其配图仅供工程师学习之用,如有内容侵权或者其他违规问题,请联系本站处理。 举报投诉

全部0条评论

快来发表一下你的评论吧 !

×
20
完善资料,
赚取积分