如何通知select()立即返回?

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

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

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

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

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

如果这是不可能的,那么第二个最佳选择是创建一个比真正的UDP套接字更糟糕的B ,并且实际上并不需要分配任何有限的资源(超出合理的内存量)。 我想Unix域套接字就可以做到这一点,但是:解决方案不应该比现在的解决方案少得多,尽管有一些适量的#ifdef东西很好。 (我主要针对Windows和Linux – 并且顺便编写C ++。)

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

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

你快到了。 使用“自我管道”技巧 。 打开一个管道,将它添加到你的select()读取和写入fd_set ,从主线程写入它以取消阻塞工作线程。 它可以跨POSIX系统移植。

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

然而,在上述系统中,这两种方法都被相当谨慎地使用,并且用于意外事件(例如,信号,终止请求)。 首选方法仍然是select()的变量超时,具体取决于为工作线程调度的时间。

使用管道而不是套接字有点清洁,因为没有其他进程可以抓住它并搞砸了。

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

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

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

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

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