我有一个包含两个线程的进程。一个线程,线程A,将设置timerfd定时器,另一个线程,线程B,将对这些定时器执行“选择”。一旦计时器到期,线程B将向线程A指示这一点。
要添加定时器,线程A将创建一个新的定时器,然后它将唤醒线程B以在其调用select中包含此定时器。我正试图通过使用文件描述符来唤醒线程B只是为了这个目的。然后,线程B将调用该FD上的select以及通过调用timerfd返回的所有FD。
问题在于我无法设法创建一个我可以控制的FD,以便在我想要时它会导致select阻塞或返回。
我试过使用shm_open来调用fcntl,我试图在文件上使用open,但是没有一个会导致select阻塞。我的所有尝试都会导致select立即返回。有没有办法创建一个FD,导致select阻塞,直到我以某种方式更新FD?
尝试1 - 使用shm_open创建FD并使用fcntl设置读锁定:
从线程A创建FD。
if((wakeUpFd = shm_open("/wakeup", O_RDWR|O_CREAT|O_TRUNC, 0)) == -1)
printf("Failed to open /wakeup, Errno = %d\n", errno);
else
{
fcntl(wakeUpFd, F_SETLK, F_RDLCK);
}
从线程A添加计时器。
#create a timer and add it to a list
/* wake up timer thread */
fcntl(wakeUpFd, F_SETLK, ~F_RDLCK);
唤醒线程B
#when select returns
if(FD_ISSET(wakeUpFd, &timerSet))
{
fcntl(wakeUpFd, F_SETLK, F_RDLCK);
}
#check all other timer FD's
尝试2 - 使用shm_open并读取/写入数据:
从线程A创建FD。
if((wakeUpFd = shm_open("/wakeup", O_RDWR|O_CREAT|O_TRUNC, 0)) == -1)
printf("Failed to open /wakeup, Errno = %d\n", errno);
else
{
if(ftruncate(wakeUpFd, 2) == -1)
{
printf("Failed with ftruncate, Errno = %d\n", errno);
}
}
从线程A添加计时器。
#create a timer and add it to a list
/* wake up timer thread */
if(write(wakeUpFd, wakeUpStr, 1) != 1)
printf("Failed to write to wakeUpFd\n");
唤醒线程B
#when select returns
if(FD_ISSET(wakeUpFd, &timerSet))
{
read(wakeUpFd, wakeUpBuf, 10);
}
#check all other timer FD's
尝试3 - 与Try 2非常相似,但使用open而不是shm_open。
尝试4 - 与Try 1相同,但使用fcntl(wakeUpFd,F_SETFL,~O_NONBLOCK)而不是fcntl(wakeUpFd,F_SETLK,~F_RDLCK)
答案 0 :(得分:4)
阅读select()
规范,特别是它所说的位:
与常规文件关联的文件描述符应始终为准备读取,准备写入和错误条件选择为true。
您无法对常规文件的文件描述符进行select()
阻止。你必须有一个管道,或套接字,或那些沿着这些行的东西,作为select()
所在的文件。
答案 1 :(得分:1)
使用Unix域套接字对,例如socketpair(AF_UNIX, SOCK_DGRAM, 0, commsd)
进行通信。当线程A创建一个新的timerfd时,它只是将新的描述符int
写入通信套接字commsd[0]
。
当线程B注意到通信套接字commsd[1]
是可读的时,它会从中读取一个或多个int
。每个int
显然都是一个新的timerd描述符,因此线程B必须将每一个添加到它select()
的集合中。
在线程B中,我建议在循环中使用非阻塞读取bytes = recv(commfd, ptr, len, MSG_DONTWAIT)
来从通信套接字读取:
char buffer[8 * sizeof(int)];
size_t head = 0, tail = 0;
ssize_t bytes;
int new_timerfd;
while (1) {
if (head >= tail)
head = tail = 0;
else
if (head > 0) {
memmove(buffer, buffer + head, tail - head);
tail -= head;
head = 0;
}
if (tail < sizeof buffer) {
bytes = recv(commsd[1], buffer + head, sizeof buffer - tail, MSG_DONTWAIT);
if (bytes > (ssize_t)0)
head += bytes;
}
if (head >= tail)
break;
while (head + sizeof (int) <= tail) {
/* Unaligned version of new_timerfd = *(int *)(buffer + head); */
memmove(&new_timerfd, buffer + head, sizeof new_timerfd);
head += sizeof (int);
/*
* Add new_timerfd to select()ed set
*/
}
}
上面的循环确实进行了额外的非阻塞recv()
调用,以检测它已经读取了所有立即挂起的内容,但这种方式非常强大。它甚至不会假设你总是可以阅读完整的int
。 (由于_POSIX_PIPE_BUF
始终是sizeof int
的倍数,您可以假设您始终阅读完整int
。)
只要有可用数据,上述循环就会从套接字执行非阻塞接收,循环体将逐个提取描述符new_timerfd
。我省略了将其添加到select()
ed集的代码。
最后,当您将更大的结构转移到循环中select()
的线程时,此方法也适用于一般情况。只需确保缓冲区足够大,至少有两个结构,然后就可以了。 memmove()
可以处理任何缓冲区打包和结构对齐问题。