【问题标题】:Posix select()/poll() and pthread IPCPosix select()/poll() 和 pthread IPC
【发布时间】:2014-06-16 11:58:37
【问题描述】:

这是一个一般性的问题 - 但是我已经多次遇到这个问题,但我仍然没有找到最好的解决方案。

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

在 POSIX(及周边环境)中是否有解决此冲突的通用技术? 我猜在 Windows 上有 WaitForMultipleObjects() 函数允许这样做。

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

【问题讨论】:

  • 你能扩展一下 “这些不适用于 select()” 吗?他们在哪些方面不与 select 和friend 合作?
  • "这些不适用于 select()" - 您不能以与套接字相同的方式(相同的调用)在互斥锁上“选择”或“轮询”,如果它必须选择相应的一个线程将等待套接字上的互斥锁/条件或事件。
  • 那是错误的思考方式; select 不应该关心你是否使用线程。
  • 这与线程无关,主要与 pthread IPC 相关——例如互斥体和条件变量。

标签: c multithreading sockets posix


【解决方案1】:

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

为此工作:

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

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

由于我比较懒惰,网上没有源码查看器,不过你可以克隆LibreVISA git树,看看src/messagepump.cpp,这个方法是用来戳worker的在另一个线程将文件描述符添加到监视列表之后的线程。

【讨论】:

  • 是的,这(pthread_kill)是我目前的思维方式。我会尝试这种方法,但我担心会有一点竞争条件 - 工作线程可以在 poll()/select() 执行之前发出信号。我也不确定性能,但这可能没问题。
  • 谢谢 - pselect() 是对比赛条件担忧的一个很好的回答。酷。
  • 我喜欢这个(尤其是 pselect + 让信号被阻塞)。
【解决方案2】:

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

我的理解是:

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

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

【讨论】:

    【解决方案3】:

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

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

    因此,您需要做两件事中的一件。要么:

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

    • 转换为更多线程模型。 IE使用一个线程发送,一个线程接收,并使用生产者/消费者模型,因此发送者线程简单地从列表/缓冲区中删除并发送(必要时阻塞),接收等待I/O(阻塞如果必要)并将其添加到列表中(这是您最后用斜体表示的内容)。

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

    【讨论】:

    • 是的,完全正确 - 在轮询/选择时不能混合使用 pthread IPC 原语和套接字。你也很好地解释了我为什么要问的原因 - 我过去使用了你的两个建议,它们很好,但是......你正确地描述了它们的缺点。
    • 我忘记的一件事是,在 linux 上,如果您担心管道的开销,您可以使用更快的 eventfd。 Simon Richter 的信号方法也很好(而且是最不丑的信号处理方法)——我赞成它——但出于可移植性的原因,信号和 pthread 的组合总是让我害怕(特别是如果你还使用fork())。跨度>
    • 'eventfd()' 在这里确实很好用,但是它不是 POSIX 而是 Linux 扩展,所以它不能移植到其他平台。
    猜你喜欢
    • 2011-06-02
    • 2018-04-20
    • 2020-04-26
    • 2011-10-01
    • 1970-01-01
    • 1970-01-01
    • 2010-11-01
    • 2017-12-04
    • 2011-01-17
    相关资源
    最近更新 更多