为什么线程用于套接字?

时间:2012-06-27 22:14:38

标签: multithreading sockets networking thread-safety

自从我发现套接字以来,我一直在使用非阻塞变体,因为我不想费心学习线程。从那以后,我在线程方面积累了很多经验,我开始问自己..为什么要用它作套接字?

线程的一个重要前提似乎是,只有当他们开始处理自己的数据集时才有意义。一旦有两个线程处理同一组数据,您将遇到如下情况:

if(!hashmap.hasKey("bar"))
{
  dostuff               // <-- meanwhile another thread inserts "bar" into hashmap
  hashmap[bar] = "foo"; // <-- our premise that the key didn't exist
                        //     (likely to avoid overwriting something) is now invalid
}

现在想象一下将远程IP映射到密码的hashmap。你可以看到我要去的地方。我的意思是,当然,这种线程交互出错的可能性非常小,但它仍然存在,为了保证一个人的程序安全,你必须考虑到每一个可能性。与简单的单线程工作流程相比,这将显着增加设计工作量。

我完全可以看到线程如何处理单独的数据集,或者显式优化以使用线程的程序。但对于“一般”案例,程序员只关心运送工作和安全程序,我找不到任何理由使用线程进行轮询。

但看到“单独的线程”方法非常普遍,也许我忽略了一些东西。开导我! :)

2 个答案:

答案 0 :(得分:4)

使用带套接字的线程有两个常见的原因,一个好,一个不太好:

理由很充分:因为您的计算机有多个CPU核心,并且您希望使用其他核心。单线程程序只能使用单个内核,因此在繁重的工作负载下,您将有一个内核固定为100%,而其他内核未使用且会浪费。

不太好的原因:您希望使用阻塞I / O来简化程序的逻辑 - 特别是,您希望避免处理部分读取和部分写入,并保持每个套接字的上下文/状态它与之关联的线程的堆栈。但是你也希望能够同时处理多个客户端,而没有慢速客户端A导致I / O调用阻塞并阻止快速客户端B的处理。

第二个原因不太好的原因是虽然每个插槽有一个线程似乎简化了程序的设计,但实际上它通常使它复杂化。它引入了竞争条件和死锁的可能性,并且难以安全地访问共享数据(如您所述)。更糟糕的是,如果您坚持使用阻塞I / O,则很难干净地关闭程序(或以任何其他方式从线程的套接字以外的任何地方影响线程的行为),因为线程通常在I / I中被阻塞O调用(可能是无限期)没有可靠的方法来唤醒它。 (信号在多线程程序中不能可靠地工作,并且回到非阻塞I / O意味着你失去了你希望的简化程序结构)

简而言之,我同意cib - 多线程服务器可能存在问题,因此除非您绝对需要使用多个内核,否则通常应该避免使用多个内核 - 即使这样,使用多个进程而不是多个线程也可能更好为安全起见。

答案 1 :(得分:2)

线程的最大优点是防止处理请求的累积滞后时间。轮询时,使用循环为状态更改的每个套接字提供服务。对于少数客户而言,这并不是很明显,但在处理大量客户时可能会导致严重的延迟。

假设每个事务需要一些预处理和后处理(取决于协议,这可能是微不足道的处理量,或者它可能是相对重要的,如BEEP或SOAP的情况)。预处理/后处理请求的总时间可能导致待处理请求的积压。

为了便于说明,假设请求的预处理,处理和后处理阶段每个消耗1微秒,因此总请求需要3微秒才能完成。在单线程环境中,如果传入的请求超过每秒334个请求(因为在1秒的时间内服务所有请求需要1.002秒),系统将变得不堪重负,导致每秒0.002秒的时间不足。但是,如果系统使用线程,那么理论上可能只需要0.336秒*(0.334用于共享数据访问+ 0.001预处理+ 0.001后处理)处理时间来完成在1秒内收到的所有请求时间段。

虽然理论上可以在0.336秒内处理所有请求,但这需要每个请求拥有自己的线程。更合理的是,通过请求数量将组合的前/后处理时间(0.668秒)加倍,并除以配置的线程数。例如,使用相同的334个传入请求和处理时间,理论上2个线程将在0.668秒(0.668 / 2 + 0.334)中完成所有请求,在0.501秒内完成4个线程,在0.418秒内完成8个线程。

如果您的守护程序收到的最高请求量相对较低,那么使用非阻塞I / O的单线程实现就足够了,但是如果您偶尔会遇到大量请求,那么值得考虑多线程模型。

我编写了多个UNIX守护进程,它们的吞吐量相对较低,而且为了简单起见,我使用了单线程。但是,当我为ISP编写自定义netflow接收器时,我使用了守护程序的线程模型,它能够处理Internet使用的高峰时间,并且系统负载平均值最小。