当我有线程时,非阻塞接收是否有用例?

6

我知道在消息传递中不常使用非阻塞接收,但是直觉告诉我它是需要的。例如,在GUI事件驱动程序中,你需要一种非阻塞等待消息的方式,这样你的程序就可以执行一些计算。解决此问题的一种方式是使用带有消息队列的特殊线程。即使你有线程,是否存在某些用例需要非阻塞接收?


1
Plachetka在胡说八道 :)。(抱歉回复不是英语,无论如何都是离题了。) - michalburger1
@Bus:这是捷克语,意思是“布发明屎”。我不太理解。 - BalusC
6个回答

4

线程和非阻塞异步操作有不同的工作方式,尽管您通常可以通过拥有执行同步操作的线程来实现相同的效果。然而,最终关键在于如何更有效地处理事情。

线程是有限资源,应该用于处理长时间运行的活动操作。如果您有某些不真正活跃做事情但需要等待一段时间以获取结果(例如调用Web服务或数据库服务器等网络上的某些I/O操作),则最好使用提供的异步替代方法,而不是将同步调用放在另一个线程上浪费线程。

您可以在此处详细了解此问题。


正好是我要回答的内容。我也会提到select(),但它并不像人们希望的那样普遍。如果一个线程可以处理500个连接,那么一个进程可能能够处理250,000个连接。 - Joshua
是的,如果一个线程可以处理1,000,000个连接,那么你也许可以处理1,000,000,000个连接!但是,当然我同意,如果你可以使用select、poll、kqueue、WaitForMultipleObjects等方法将一个线程多路复用到一堆连接上,那么它会更好地扩展。 - MK.
1
在Windows平台上,您可以使用I/O完成端口和异步I/O来处理数万个活动连接和挂起的I/O请求,只需非常少量的线程(我们说的是2-4个线程)... - Len Holgate
1
在达到神话般的十亿之前,你会遇到其他问题。首先,操作系统可能会在“几千个同时连接”的范围内崩溃... - Donal Fellows
谢谢您的回答,但我想听一些不考虑线程数量有限这个事实的论点,因为这可以很容易地改变。请看看我的回答并发表评论,谢谢!我很快就会阅读这篇文章... - Gabriel Ščerbák

3

每个连接一个线程通常不是个好主意(浪费内存,不是所有的操作系统都能处理大量线程等)。

如何中断阻塞接收调用呢?例如在Linux上(以及可能在其他一些POSIX操作系统上),pthread + 信号 = 灾难。使用非阻塞接收,您可以将等待接收套接字和用于在线程之间通信的某种IPC套接字多路复用。这也相对容易地适用于Windows世界。

如果您需要用更复杂的东西(例如OpenSSL)替换普通套接字,则依赖阻塞行为可能会让您陷入麻烦。例如,OpenSSL可能会在阻塞套接字上死锁,因为SSL协议具有发送/接收倒置场景,其中在完成某些发送之前无法继续接收。

我的经验是——“当怀疑时,请使用非阻塞套接字”。


嗯,你看到潜在死锁的问题了,这很有趣。你还默许地提到线程间的同步和通信,你能否提供一些极简例子,在那里实现线程间通信比有效调用非阻塞操作更难?这就是我在我的问题答案中暗示的东西... - Gabriel Ščerbák
这里有一个线程讨论 OpenSSL 中由于阻塞套接字引起死锁的问题:http://marc.info/?t=114988489900003&r=3&w=2 我不确定我完全理解你问100%的第二部分,但基本上我有一个线程池处理多个连接;你不能优雅地实现这个功能而不使用非阻塞套接字。异步套接字(IO完成)会让事情变得更加美好(但有些难以理解),但非阻塞事件驱动套接字既提供了简单性,又提高了效率。 - MK.
我强烈建议您查看http://www.mailinator.com/tymaPaulMultithreaded.pdf。 - Peter Štibraný
我不能很有效地阅读PowerPoint幻灯片,但他的基准测试没有源代码,我怀疑他在NIO测试中为所有连接使用了1个线程,这不是最好的做法,你应该有一个线程处理5-60个连接,具体取决于一些因素。此外,对我来说并不清楚从Java到其他所有内容的推断是否干净利落。 - MK.

2

使用阻塞IO,在面对缓慢、挂起或断开连接的客户端/服务时,让你的应用程序进行最佳努力有序关闭是具有挑战性的。

使用非阻塞IO,你可以在系统调用返回时立即终止正在进行的操作。如果你的代码考虑了过早终止(这在非阻塞IO中相对简单),这可以允许你优雅地清理保存的状态。


1

我想不出任何例子,但有时非阻塞API的设计方式使它们比显式多线程实现更易于使用/更直观。


我认为现在有更多的并发模型,这些模型往往更易于使用,因此我认为这不是非常有效的。 - Gabriel Ščerbák

1

这里有一个我最近遇到的真实情况。以前,我有一个由 crontab 管理的脚本,每小时运行一次,但有时用户会登录到机器上并手动运行脚本。这会带来一些问题,例如 crontab 和用户同时执行可能会导致问题,有时用户会以 root 身份登录 - 我知道,这是错误的模式,不在我的控制范围内 - 并以错误的权限运行脚本。因此,我们决定将例程作为守护进程运行,具有适当的权限,现在用户使用的命令只会触发守护进程。

所以,这个用户执行的命令基本上会做两件事:触发守护进程并等待它完成任务。但它还需要超时和在等待期间持续转储守护程序日志给用户。

如果我理解你提出的情况,我有你想要的案例:我需要在独立与用户交互的同时保持从守护程序中收听。解决方案是异步读取。

幸运的是,我没有考虑使用线程。如果我在编写 Java 代码,可能会想到这一点,但这是 Python 代码。


非阻塞接收的好例子,但是我不明白你认为这比线程更好在哪里。不过,我给了你点赞,感谢你所做出的经验和努力。 - Gabriel Ščerbák
2
对我来说,更好的解决方案是尽可能简单地解决问题 :-)。正如我所说,如果我使用Java,我可能会使用线程,因为Java线程支持非常好,同步也很简单,而在Python中,这种支持不太好。我想每个解决方案都有其优缺点,当你说一个更好时,你必须有参数来确定在你的情况下最重要的事情。到目前为止,我的线程经验对于在C++和Java中具有共享内存的重型多任务处理效果很好,但在Perl和Python中使用fork获得了更好的结果。 - lfagundes

0

我的观点是,当我们考虑线程和消息传递的完美时,真正需要权衡的是为计划非阻塞接收操作编写调度程序以及为具有共享状态的线程编写同步代码(锁等)。我认为,这两个方面有时很容易,有时很困难。因此,使用情况可能是存在许多异步消息要接收,并且基于这些消息有大量数据需要操作。在一个线程中使用非阻塞接收将非常容易,而在许多线程和共享状态下则需要进行更多的同步...我还在考虑一些现实生活中的例子,可能稍后会加入。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接