C语言 Posix select()/poll() and pthread IPC



这是一个普通的问题,但我已经遇到这个问题好几次了,仍然没有找到最好的解决方案。

假设您有一个程序(例如HTTP应用程序服务器(,它是多线程的,通过套接字(TCP、Unix…(进行通信。主线程使用异步IO和select((或poll((POSIX调用来调度套接字之间的流量。还有一些工作线程处理请求并提供响应。为了将响应发送回客户端,工作线程"以某种方式"与主线程(轮询(同步。问题的核心是"如何",即什么是有效的。我可以使用pipe((-基于套接字的IPC机制,但这在我看来是相当大的开销。我倾向于使用一些pthread IPC技术,如互斥、条件变量等……但这些技术不适用于select((或poll((。

POSIX(和周围环境(中是否有解决这种冲突的通用技术?我想在Windows上有一个WaitForMultipleObjects((函数可以实现这一点。

示例程序是为了说明一个问题而制作的,我知道我可以用不同的方式设计主/工作模式,但这不是我想要的。我在其他情况下也有同样的情况

您可以使用一个信号来戳工作线程,这将中断select()调用并返回EINTR。使用pselect()可以更容易地做到这一点。

为此:

  1. 决定信号(或分配实时信号(
  2. 附加一个空的处理程序函数(如果忽略该信号,系统调用将自动重新启动(
  3. 至少在工作线程中阻止信号
  4. 在等待时使用pselect()中的信号掩码参数来解锁信号

在线程之间,可以使用pthread_kill将信号专门传递给工作线程。当另一个进程应该发送信号时,您可以确保信号在除工作线程之外的所有线程中都被阻塞(因此它将被传递到那里(,或者使用信号处理程序来查明信号是否被发送到工作线程,并使用pthread_kill显式转发它(工作线程仍然不需要在信号处理程序中做任何事情(。

由于我的懒惰,我没有在线的源代码查看器,但你可以克隆LibreVISA git树,并查看src/messagepump.cpp,在另一个线程向监视列表添加文件描述符后,该方法用于戳工作线程。

Simon Richthers的回答很好。另一种选择可能是使主线程只负责侦听新连接,并使用连接信息启动工作线程,以便工作线程负责来自该源的所有后续"事务"。

我的理解是:

  1. 主线程使用select
  2. 工作线程处理由主线程转发给它的请求
  3. 因此需要在工作线程和主线程之间同步,例如在工作者完成事务需要将响应发送回main线程,该线程又将响应转发回源

为什么不通过让工作线程负责特定连接中的所有事务来消除必须在工作线程和主线程之间同步的问题呢?因此,主线程只负责侦听新连接,并用连接信息(即新连接的文件描述符(启动工作线程。

首先,唤醒另一个线程的方法是使用线程A中的pthread_cond_wait/pthread_cond_timedwait调用来等待,而线程B使用pthread_cond_broadcast/pthread_cond_signal来拾取它。因此,例如,如果B是生产者,而a是消费者,生产者可能会将项目添加到受互斥锁保护的链表中。将有一个相关的条件变量,这样在添加项目后,它可以唤醒线程B,这样它就可以查看是否有新项目到达列表中,如果有,则将其删除。我说"associated",这样就可以将相同的互斥对象与保护列表的条件变量相关联。

到目前为止还不错。现在您提到异步I/O。我多次想做的是在一组FD上的select()poll()一组条件变量上,所以当条件变量被广播到时,select()poll()会被中断。没有简单的方法可以直接做到这一点;你不能简单地混搭。

因此,你需要做两件事中的一件。任一:

  • 解决问题(例如,使用自连接的pipe()发送一个字节来唤醒select(),而不是条件变量和条件变量,或者从等待条件变量的某个附加线程;或者

  • 转换为线程化程度更高的模型。IE使用一个线程进行发送,一个线程用于接收,并使用生产者/消费者模型,因此发送方线程只需从列表/缓冲区中删除并发送(必要时阻止(,而接收到的线程则等待I/O(必要时阻塞(并将其添加到列表中(这是末尾用斜体表示的内容(。

第二个是对我们这些在异步I/O方面长大的人的一个重大设计更改,而第一个是丑陋的。你不是第一个对此感到沮丧的人,但我并没有找到一个简单的方法来解决这个问题。第一个是效率低下,如果你只写一个字符来唤醒自管道的选择循环,我认为你不会看到太多的效率低下。

最新更新