什么时候需要“成千上万”的线程?


31

Erlang,Go和Rust都以一种或另一种方式声称它们支持使用廉价的“线程” /协程进行并发编程。在转到FAQ状态:

在同一个地址空间中创建数十万个goroutine是很实际的。

防锈教程说:

由于创建任务比传统线程便宜得多,因此Rust可以在典型的32位系统上创建数十万个并发任务。

Erlang的文档说:

为了支持具有数十万甚至数百万个进程的Erlang系统,默认的初始堆大小为233个字是非常保守的。

我的问题:哪种应用程序需要这么多并发执行线程?只有最繁忙的Web服务器才能同时接收数千名访问者。当线程/进程的数量远大于物理核心的数量时,我编写的老板/工作调度型应用程序命中率递减。我想这对于数字应用程序可能有意义,但实际上,大多数人将并行性委托给以Fortran / C / C ++编写的第三方库,而不是这些较新一代的语言。


5
我认为您的困惑的根源在于:这些微线程/任务/等并不是主要用于替代您所讨论的OS线程/进程,也不是要用于划分易于并行化的数字处理大块在几个内核之间(您正确地指出,为此目的在4个内核上没有100k线程是没有意义的)。
us2012 2013年

1
那是什么意思呢?也许我是一个幼稚的人,但是我从未遇到过引入协程/ etc会简化单线程执行程序的情况。而且我已经能够实现流程的“低”并发水平,在Linux上,我可以不费吹灰之力地启动成百上千个。
user39019 2013年

实际上有许多任务在起作用,这毫无意义。这并不意味着您就不能拥有大量的任务,而这些任务大多只是为了等待某些事情而被阻塞。
罗伦·佩希特尔

5
基于任务的异步与基于线程的异步的思想是,用户代码应该专注于需要执行的任务,而不是管理执行这些任务的工作人员。将线程视为您雇用的工人;雇用工人是很昂贵的,如果您这样做,您希望他们100%地努力完成尽可能多的任务。大量系统的特征是具有成百上千的待处理任务,但是您不需要成百上千的工作人员。
埃里克·利珀特

继续@EricLippert的评论,在某些情况下将存在成千上万的任务。示例1:分解数据并行任务,例如图像处理。示例2:支持数十万个客户端的服务器,每个客户端都可能随时发出命令。每个任务都需要有自己的“轻量级执行上下文”-能够记住其处于什么状态(通信协议),当前正在执行的命令以及其他功能。轻量级是可能的,只要每个都具有浅调用堆栈即可。
rwong

Answers:


19

一个用例-websockets:
与简单请求相比,websockets的寿命长,因此在繁忙的服务器上,随着时间的推移,许多websockets会累积。微线程为您提供了良好的概念建模以及相对容易的实现。

一般而言,许多或更少的自治单元正在等待某些事件发生的情况应该是很好的用例。


15

考虑一下Erlang最初旨在做什么,即管理电信,可能会有所帮助。诸如路由,交换,传感器收集/聚合等活动。

将其带入网络世界-考虑使用Twitter之类的系统。该系统可能不会在生成网页时使用微线程,但可以在其tweet的收集/缓存/分发中使用它们。

本文可能会提供进一步的帮助。


11

在不允许修改变量的语言中,维护状态的简单动作需要一个单独的执行上下文(大多数人将其称为线程,而Erlang则将其称为进程)。基本上,一切都是工人。

考虑以下这个Erlang函数,该函数维护一个计数器:

counter(Value) ->
    receive                               % Sit idle until a message is received
        increment -> counter(Value + 1);  % Restart with incremented value
        decrement -> counter(Value - 1);  % Restart with decremented value
        speak     ->
            io:fwrite("~B~n", [Value]),
            counter(Value);               % Restart with unaltered value
        _         -> counter(Value)       % Anything else?  Do nothing.
    end.

在像C ++或Java这样的常规OO语言中,您可以通过创建一个带有私有类成员的类,用于获取或更改其状态的公共方法以及每个计数器的实例化对象来实现。Erlang用进程替换了实例化对象的概念,用消息替换了方法的概念,并通过尾部调用来维护状态,这些尾部调用使用组成新状态的任何值重新启动函数。该模型以及大多数Erlang的存在理由的隐藏优点是,该语言通过使用消息队列自动序列化对计数器值的访问,从而使并发代码非常容易实现,并且具有高度的安全性。

您可能已经习惯了上下文切换很昂贵的想法,从主机OS的角度来看,这仍然是正确的。Erlang运行时本身是一个经过微调的小型操作系统,因此在自己的进程之间进行切换是快速而高效的,同时还可以将OS所做的上下文切换次数降至最低。因此,拥有成千上万的流程不是问题,应予以鼓励。


1
您的上一个应用程序counter/1应使用小写字母c;)我试图对其进行修复,但StackExchange不喜欢1个字符的编辑。
d11wtq

4

我的问题:哪种应用程序需要这么多并发执行线程?

1)语言“可扩展”的事实意味着,当事情变得越来越复杂时,您不得不抛弃该语言的机会就更少了。(这被称为“整体产品”概念。)正是出于这个原因,许多人放弃了Nginx的Apache。如果您接近线程开销所施加的“硬限制”,您将感到恐惧并开始思考克服它的方法。网站永远无法预测它们将获得多少流量,因此花一点时间使事物具有可伸缩性是合理的。

2)每个请求一个goroutine只是开始。在内部使用goroutine的原因很多。

  • 考虑一个具有100个同时请求的Web应用程序,但是每个请求都会生成100个后端请求。一个明显的例子是搜索引擎聚合器。但是,几乎任何应用程序都可以为屏幕上的每个“区域”创建goroutine,然后独立生成它们而不是顺序生成它们。例如,Amazon.com上的每个页面均由150多个后端请求组成,这些请求仅为您组装。您不会注意到,因为它们是并行的,不是顺序的,并且每个“区域”都是它自己的Web服务。
  • 考虑任何可靠性和延迟至关重要的应用。您可能希望每个传入的请求触发一些后端请求,并返回最先返回的数据
  • 考虑在您的应用中完成的任何“客户端加入”。您可以说出一堆goroutine,而不是说“为每个元素获取数据”。如果您要查询一堆从数据库,那么您将神奇地快N倍。如果您不这样做,它的速度将不会变慢。

当线程/进程数远远大于物理核心数时,命中收益递减

性能并不是将程序分解为CSP的唯一原因。它实际上可以使程序更易于理解,并且可以用更少的代码来解决某些问题。

就像上面链接的幻灯片一样,代码中的并发是解决问题的一种方法。没有goroutine就像没有您的语言中的Map / Dictonary / Hash数据结构。没有它,您可以渡过难关。但是一旦有了它,就可以在任何地方使用它,它确实简化了您的程序。

过去,这意味着“自行开发”多线程编程。但这是复杂而危险的-仍然没有很多工具可以确保您不参加比赛。以及如何防止将来的维护者犯错误?如果您查看大型/复杂程序,就会发现它们在该方向上消耗了很多资源

由于并发并不是大多数语言的头等舱部分,因此当今的程序员对为什么它将对他们有用的问题视而不见。随着每部手机和手表都朝着1000核前进,这一点将变得更加明显。Go带有内置的种族检测器工具。


2

对于Erlang,通常每个连接或其他任务只有一个进程。因此,例如,流音频服务器每个连接的用户可能有1个进程。

通过使上下文切换非常便宜,对Erlang VM进行了优化以处理成千上万个进程。


1

方便。早在我开始进行多线程编程时,我就一边玩很多模拟和游戏开发一边玩。我发现为每个对象剥离一个线程并让它自己做而不是通过循环处理每个对象是非常方便的。如果您的代码不受不确定性行为的干扰,并且没有冲突,则可以简化编码。有了现在我们可以使用的功能,如果我要回到这方面,我可以轻松地想象一下,由于具有足够的处理能力和内存来处理那么多离散对象,因此会拆分成千上万个线程!


1

Erlang的一个简单示例被设计用于通信:传输网络数据包。当您执行一个http请求时,您可能有数千个TCP / IP数据包。此外,每个人都可以同时连接,您就拥有了用例。

考虑任何大公司内部用于处理订单或可能需要的任何应用程序。Web服务器不是唯一需要线程的东西。


-2

这里想到一些渲染任务。如果您要对图像的每个像素执行一连串的操作,并且这些操作是可并行化的,那么即使是相对较小的1024x768图像也都在“数十万”括号内。


2
几年前,我花了几年时间进行实时FLIR图像处理,以每秒30帧的速度处理256x256图像。除非您有大量的硬件处理器,并且要在它们之间进行数据分区的无缝方式,否则您要做的最后一件事就是增加上下文切换,内存争用和缓存颠簸到实际的计算成本。
约翰·斯特罗姆

这取决于完成的工作。如果您要做的只是将工作移交给硬件核心/执行单元,之后您可以有效地将其遗忘(并注意这是GPU的工作方式,因此这不是一种假设的情况),那么该方法就是有效。
Maximus Minimus
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.