2

假设我们有一个即时消息应用程序,基于客户端-服务器,而不是 p2p。实际的协议并不重要,重要的是服务器架构。可以将所述服务器编码为使用非阻塞套接字以单线程、非并行模式运行,根据定义,这允许我们立即(或立即)有效地执行诸如读写之类的操作。非阻塞套接字的这一特性允许我们在服务器的最核心使用某种选择/轮询功能,几乎没有时间浪费在实际的套接字读/写操作上,而是花时间处理所有这些信息. 据我了解,正确编码,这可以非常快。但是还有第二种方法,那就是积极地多线程,创建一个新线程(显然使用某种线程池,因为在某些平台和某些情况下,该操作可能(非常)慢),并让这些线程并行工作,而主后台线程处理 accept() 和其他东西。我已经在网上的各个地方看到过这种方法的解释,所以它显然确实存在。

现在的问题是,如果我们有非阻塞套接字、即时读/写操作以及简单、易于编码的设计,为什么还要存在第二种变体?我们试图用第二种设计克服什么问题,即线程?AFAIK 这些通常用于解决一些缓慢且可能阻塞的操作,但那里似乎不存在此类操作!

4

1 回答 1

1

我假设您不是在谈论每个客户端有一个线程,因为这样的设计通常是出于完全不同的原因,而是一个线程池,每个线程池处理多个并发客户端。

该架构与单线程服务器相比的原因仅仅是为了利用多个处理器。您所做的工作不仅仅是 I/O。你必须解析消息,做各种工作,甚至可能运行一些更重量级的加密算法。这一切都需要CPU。如果您想扩展,利用多个处理器将允许您扩展更多,和/或使每个客户端的延迟更低。

这种设计中的一些收益可能会被您在多线程环境中可能需要更多锁定的事实所抵消,但做得正确,当然取决于您在做什么,这可能是一个巨大的胜利 - 以牺牲为代价更复杂的。

此外,这可能有助于克服操作系统限制。内核中的 I/O 路径可能在处理器之间分布得更多。并非所有操作系统都可能完全能够从单线程应用程序线程化 IO。回到过去,旧的 *nix select() 并没有所有很好的替代品,它的文件描述符限制通常为 1024,一旦你告诉它监视太多的套接字,类似的 API 就会严重开始降级。将所有这些客户端分布在多个线程或进程上有助于克服这一限制。

至于线程之间的 1:1 映射,实现该架构有几个原因:

  • 更简单的编程模型,这可能会导致发现错误的难度降低,并且实现速度更快。

  • 支持阻塞 API。这些到处都是。让一个线程处理许多/所有客户端,然后继续对数据库进行阻塞调用,这将使每个人都停滞不前。即使读取文件也会阻塞您的应用程序,并且您通常无法监视常规文件句柄/描述符的 IO 事件 - 或者当您可以时,编程模型通常异常复杂。

这里的缺点是它不会扩展,至少不能使用最广泛使用的语言/框架。拥有数千个本机线程会损害性能。尽管有些语言在这里提供了更轻量级的方法,例如 Erlang 和某种程度上的 Go。

于 2011-01-13T18:23:32.093 回答