【问题标题】:How to signal select() to return immediately?如何发出信号 select() 立即返回?
【发布时间】:2010-09-27 22:00:02
【问题描述】:

我有一个工作线程正在侦听 TCP 套接字以获取传入流量,并缓冲接收到的数据以供主线程访问(我们将此套接字称为 A)。但是,工作线程也必须做一些常规操作(比如每秒一次),即使没有数据进来。因此,我使用带有超时的select(),这样我就不需要继续轮询. (请注意,在非阻塞套接字上调用receive() 然后休眠一秒钟是不好的:传入的数据应该立即可用于主线程,即使主线程可能并不总是能够立即处理它,因此需要缓冲。)

现在,我还需要能够立即向工作线程发出信号以执行其他操作;从主线程,我需要让工作线程的select() 立即返回。目前,我已经解决了这个问题(基本上采用herehere的方法):

在程序启动时,工作线程为此目的创建一个额外的数据报 (UDP) 类型的套接字,并将其绑定到某个随机端口(我们称之为套接字B)。同样,主线程创建一个用于发送的数据报套接字。在对select() 的调用中,工作线程现在在fd_set 中列出了AB。当主线程需要发信号时,它sendto() 的几个字节到localhost 上的相应端口。回到工作线程,如果在select() 返回后B 仍留在fd_set 中,则调用recvfrom() 并简单地忽略接收到的字节。

这似乎工作得很好,但我不能说我喜欢这个解决方案,主要是因为它需要为 B 绑定一个额外的端口,还因为它添加了几个额外的套接字 API 调用我猜可能会失败——而且我真的不想为每种情况找出适当的行动。

我认为理想情况下,我想调用一些以 A 作为输入的函数,除了让select() 立即返回之外什么都不做。但是,我不知道这样的功能。 (我想我可以使用例如shutdown() 套接字,但副作用是不能接受的:)

如果这不可能,第二好的选择是创建一个 B,它比真正的 UDP 套接字更笨,并且不需要分配任何有限的资源(超出合理的数量)内存)。我猜Unix domain sockets 会做到这一点,但是:该解决方案不应该比我目前拥有的跨平台少得多,尽管一些适量的#ifdef 东西很好。 (我主要针对 Windows 和 Linux ——顺便写一下 C++。)

请不要建议重构以摆脱两个单独的线程。这种设计是必要的,因为主线程可能会被阻塞很长时间(例如,做一些密集的计算——我不能从最里面的计算循环开始定期调用receive()),同时,有人需要缓冲传入的数据(由于我无法控制的原因,它不能是发送者)。

现在我正在写这篇文章,我意识到肯定有人会简单地回复“Boost.Asio”,所以我只是第一次看到它......但找不到明显的解决方案。请注意,我也不能(轻松)影响套接字 A 的创建方式,但如果需要,我应该能够让其他对象包装它。

【问题讨论】:

    标签: c multithreading select sockets


    【解决方案1】:

    你快到了。使用"self-pipe" trick。打开一个管道,将它添加到您的select() 读写fd_set,从主线程写入它以解除阻塞工作线程。它可以跨 POSIX 系统移植。

    我在一个系统中看到了一种类似的 Windows 技术变体(实际上与上述方法一起使用,以#ifdef WIN32 分隔)。可以通过向fd_set 添加一个虚拟(未绑定)数据报套接字然后关闭它来实现解除阻塞。当然,缺点是每次都必须重新打开它。

    但是,在上述系统中,这两种方法都很少使用,并且用于意外事件(例如,信号、终止请求)。首选方法仍然是对select() 的可变超时,具体取决于为工作线程安排的时间。

    【讨论】:

    • 谢谢,但正如问题中所指出的,最终的解决方案也应该在 Windows 中工作。
    • 好的,感谢 Windows 的建议。知道在实践中重新开放有多昂贵吗?
    • 感谢 Checkers,创建和关闭(非绑定)套接字似乎确实工作得很好。
    • 我没有任何确切的数字,但是由于您是为 Windows 编写的,它几乎总是意味着桌面,这意味着成本应该可以忽略不计。
    • 在 Windows 下,您也可以只保持 UDP 套接字打开并向其发送数据包以使 select() 在该套接字上返回准备好读取,或者您可以创建一对 TCP 套接字彼此连接并在其中一个上发送一个字节,而select() 正在监视另一个。
    【解决方案2】:

    使用管道而不是套接字更干净一些,因为其他进程不可能抓住它并搞砸事情。

    使用 UDP 套接字肯定会产生杂散数据包进入和干扰的可能性。

    匿名管道将永远不可用于任何其他进程(除非您将其提供给它)。

    您也可以使用信号,但在多线程程序中,您需要确保除您想要的线程之外的所有线程都屏蔽了该信号。

    【讨论】:

      【解决方案3】:

      在 unix 上,使用管道会很简单。如果您在 Windows 上并希望继续使用 select 语句来保持您的代码与 unix 兼容,那么创建一个未绑定的 UDP 套接字并关闭它的技巧非常简单。但是你必须使它成为多线程安全的。

      我发现使这种多线程安全的唯一方法是在运行 select 语句的同一线程中关闭并重新创建套接字。当然,如果线程在选择上阻塞,这很困难。然后在windows中调用QueueUserAPC。当 windows 在 select 语句中阻塞时,线程可以处理异步过程调用。您可以使用 QueueUserAPC 从不同的线程安排此操作。 Windows 中断选择,在同一个线程中执行您的函数,然后继续选择语句。您现在可以在您的 APC 方法中关闭套接字并重新创建它。保证线程安全,您永远不会丢失信号。

      【讨论】:

        【解决方案4】:

        简单来说: 一个全局变量保存套接字句柄,然后关闭全局套接字,select() 将立即返回:closesocket(g_socket);

        【讨论】:

          猜你喜欢
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2013-11-02
          • 1970-01-01
          • 2018-02-12
          • 2012-01-30
          • 1970-01-01
          • 1970-01-01
          相关资源
          最近更新 更多