译 | Concurrency is not Parallelism

来源:cyningsun.github.io/12-09-2019/…html

目录

Concurrency vs Parallelism

若是你看看今天的编程语言,可能会发现这个世界是面向对象的,但实际上并不是如此,世界是并行的。你经过网络等等,从最底层(例如:多核计算机)获取全部的东西,一路攀升到了解星球、宇宙。世界上全部的东西都在同时发生,但咱们拥有的计算工具实际上并不擅长表达这种世界观。看起来彷佛是一种失败,若是咱们了解什么是并发性,以及如何使用它,就能够解决这个问题。

我将假设大家中的大多数人至少据说过 Go 编程语言,它也是我最近几年在 Google 工做的内容。Go 是一种并发语言,也就是说它使并发性有用,有像上帝同样同时执行事物的能力;有在同时执行的事物之间进行通讯的能力;有叫作 select 语句的东西,它是一个多路并发控制开关。若是你搞不懂是怎么回事,不用担忧。

在大约两年前,当咱们发布 Go 时,在场的程序员都说:”哦,并发工具,我知道作什么的,并行运行,耶“。但实际并不是如此,并发性和并行性是不同的,这是个常被误解的问题。我在这里试图解释缘由,并向您展现并发性实际上更好。那些困惑的人会碰到什么事情:他们执行的程序,在更多的处理器上会变得更慢。他们会认为有问题,无论用,想要逃开。但真正有问题的是世界观,我但愿我能改正它。

什么是并发性?并发性,如我当前使用的同样,用于计算机科学领域,是一种构建事物的方法。它是由独立执行的事物组成,一般是 function,虽然不必定必须如此。咱们一般称之为交互式进程。称其为进程,并非指 Linux 进程,指的是一种广泛的概念,它包括线程、协程、进程等等,因此尽量抽象的理解。并发性由独立执行的进程组成;

另外一方面,并行性是同时执行多个事物,可能相关,也可能无关。

若是你用语焉不详的方式思考,并发性是指同时负责不少事情;并行性是指同时作不少事情。它们显然是相关的,但其实是不一样的概念,若是没有合适的工具包,尝试思考它们会有些困惑。一个是关于结构并发性,另外一个是关于执行并行性。我会告诉你为何这些概念很重要。并发性是一种构造事物的方法,以即可以使用并行性更好地完成工做。但并行性不是并发性的目标,并发性的目标是好的结构。

An analogy

若是你在运行一个操做系统的话,会很熟悉的一个类比。操做系统可能有鼠标驱动程序、键盘驱动程序、显示驱动程序、网络驱动程序等等,都是由操做系统管理的,内核中的独立事物。它们都是并发的事物,却不必定是并行的。若是只有一个处理器,同一时间其中只有一个处于运行。I/O 设备具备一个并发模型,但本质不是并行的,它不须要是并行的。并行的事物可能相似向量点积,能够分解为微观操做,以使得能够在一些精美的计算机上并行执行。很是不一样的概念,彻底不是同一个东西。

Cocurrency plus communication

为了能利用并发性,必须添加 communication 的概念。我今天不会聚焦于该概念太多,但一下子你会看到一点点。并发性提供了一种将程序构造为独立块的方法,而后,必须使这些块协调一致。要使之工做,须要某种形式的 communicationTony Hoare 在1978年写了一篇论文叫作 《communicating sequential processes》,实在是计算机科学中最伟大的论文之一。若是你还没读过,要是从本次演讲中真有什么领悟的话,回家你应该读读那篇论文。它太难以想象了,基于此论文,不少人未进行太多考虑就遵循、并构建工具,以将其思想运用到并发语言中,好比另外一种很棒的语言Erlang。GO 中也有其一些思想,关键点都在原稿中,除了稍后会提到的几个小例外。

Gophers

但,你看这一切太抽象了。咱们须要 Gopher 的帮忙,来一些 Gopher。

有一个真正的问题咱们要解决。有一堆过期的手册,能够是 C++98 手册,如今已是 C++11;或许是 C++11 书籍,但再也不须要了。关键是咱们想要清理掉它们,它们占用了大量空间。因此咱们的 Gopher 有一个任务,把书从书堆里取出来,放到焚化炉里清理掉。可是,若是是一大堆书,只有一个 Gopher 须要很长时间。Gopher 也不擅长搬运书籍,尽管咱们提供了小车。

因此再增长一个 Gopher 来解决这个问题,只有 Gopher 不会好起来,对吧?

由于它须要工具,无可厚非,咱们须要提供全部它须要的东西。Gopher 不只须要做为 Gopher 的能力,也须要工具来完成任务。再给它一辆车,如今这样应该会更快。在两个 Gopher 推车的状况下,确定能更快地搬运书。但可能存在一个小问题,由于咱们必须使它们同步。来回奔波中,书堆互相妨碍,它们可能会被困在焚化炉里,因此它们须要协调一点。因此你能够想象 Gopher 们发送 Tony Hoare 的短信息,说:我到了,我须要空间把书放进焚化炉。无论是什么,但你明白了,这很傻。但我想解释清楚,这些概念并不深入,它们很是好。

如何让它们搬运得更快,咱们把一切都增长一倍。咱们提供两个 Gopher,把书堆,焚化炉和 Gopher 同样也增长一倍。如今咱们能够在相同的时间里搬运两倍的书,这是并行,对吧?

可是,想象它不是并行,而是两个 Gopher 的并发组合。并发性是咱们表达问题的方式,两个 Gopher 能够作到这一点。咱们经过实例化 Gopher 程序的更多实例来并行,这被称为进程(在此状况下称为 Gopher)的并发组合。

如今这种设计不是自动并行的。确实有两个 Gopher,可是谁说它们必须同时工做呢?我能够说,同时只有一个 Gopher 能够移动,就像单核计算机,此设计仍然是并发的,很好很正确,但它本质上不是并行的,除非让两个 Gopher 同时搬运。当并行出现时,有两个事物同时执行,而不只仅是拥有两个事物。这是一个很是重要的模型,一旦判定理解了它,咱们就会明白能够把问题分解成并发的块。

咱们能够想出其余模型,下面有一个不一样的设计。在图中有三个 Gopher,同一堆书,同一个焚化炉,可是如今有三个 Gopher。有一个 Gopher,它的工做就是装车;有一个 Gopher,它的工做就是推车,而后再把空的还回去;还有一个 Gopher,它的工做就是装入焚化炉。三个 Gopher,速度理应会更快。但可能不会快多少,由于它们会被阻塞。书可能在错误的地方,在那里没有什么须要用车来作的。

让咱们处理下这个问题,另外增长一个Gopher归还空车,这明显很傻。但我想指出一个至关深入的问题,这个版本的问题实际上会比以前版本的问题执行得更好。尽管为了完成更多的工做,增长了一个 Gopher 来回奔波。所以,一旦咱们理解了并发性的概念,就能够向图片增长 Gopher,真的能够作更多的工做,使之运行得更快。由于管理的更好的块的并发组合真的能够运行得更快。工做可能不会刚好完美地进行,可是能够想象若是全部的 Gopher 的时间都恰到好处,它们知道每次搬运多少书。并发组合真的可让4个 Gopher 都一直在忙碌。事实上,此版本可能比原来的版本快四倍。虽然可能性不大,可是我想让你理解,是可能的。

此时有一个发现,它很是重要并且很微妙,有些柔性。咱们在现有的设计中经过添加并发程序来提升程序的性能。咱们真的添加了更多的东西,整个过程变得更快了。若是仔细想一想,有点奇怪,也有点不奇怪。由于额外添加了一个 Gopher,并且 Gopher 确实工做。可是若是你忘记了它是个 Gopher 的事实,并认为只是增长了东西,设计确实可使它更高效。并行性能够出自于对问题更好的并发表达,这是一个至关深入的看法。由于 Gopher 们的参与因此看起来不像。可是不要紧。

此时有四个进程并发运行。一个 Gopher 将东西装到车中;一个 Gopher 把车运到焚化炉;还有另外一个 Gopher 将车中的物品卸到焚化炉中;第四个 Gopher 把空车还回来。您能够将它们视为独立的进程,彻底独立运行的进程,咱们只是将它们并行组合以构建完整的程序解决方案。这不是咱们惟一能够构造的方案,如下是一个彻底不一样的设计。

经过增长另一个堆书、焚化炉、和4个 Gopher,可使该设计更加并行。但关键是,采用已有概念以分解问题。一旦清楚这是并发分解,就能够在不一样的纬度上使其并行化。不管可否得到更好的吞吐量,可是至少,咱们得以更细粒度的理解问题,能够控制这些块。在此状况下,若是一切恰好,会有8个 Gopher 努力以烧掉那些C++手册。

固然,也许根本没有发生并行,谁说这些 Gopher 必须同时工做,我可能每次只能运行一个 Gopher。在这种状况下,该设计只能像原始问题同样,以单个 Gopher 的速率运行。它执行时,其余7个将所有空闲。但该设计仍然是正确的。这很了不起,由于意味着咱们在保证并发性时没必要担忧并行性。若是并发性正确,并行性其实是一个自由变量,决定有多少个 Gopher 处于忙碌。咱们能够为整个事情作一个彻底不一样的设计。

让咱们忘记将旧模式投入到新模式。在故事中有两个 Gopher,再也不让一个 Gopher 从书堆一直运到焚化炉,而是在中间加入暂存区。所以,第一个 Gopher 将书籍搬运到暂存区,将它们丢下,跑回去再运另一些。第二个 Gopher 坐在那里等待书达到暂存区,并把书从该位置搬运到焚化炉。若是一切良好,则有两个 Gopher 进程运行。它们是相同的类型,但有些细微不一样,参数略有不一样。若是系统将正常运行,一旦启动,其运行速度就会是原始模式的两倍。即便某些方面说它是彻底不一样的设计。一旦咱们有了这个组合,咱们能够采起另外的作法。

将以惯常的作法使其并行,同时运行整个程序的两个版本。翻倍以后,有了4个 Gopher,吞吐量将高达四倍。

或者,咱们能够采用另外一种作法,在刚才的并发多 Gopher 问题中,在中间加入暂存区。所以,如今咱们有8个 Gopher 在运行,书籍很是快的速度被焚烧。

但这样还不够好,由于咱们能够在另外一个维度并行,运力全开。此时,有16个 Gopher 将这些书搬运到焚化炉中。显然,增长 Gopher 使问题解决的更好,是很是简单和愚蠢的。但我但愿您了解,从概念上讲,这真的就是您考虑并行运行事物的方式。您无需考虑并行运行,而是考虑如何将问题以可分解、可理解、可操做的方式,分解为独立的块,而后组合起来以解决整个问题。

Lesson

以上就是的全部例子有什么意义呢?

首先,有不少方法能够作到这一点,我刚刚展现了一些。若是你坐在那里拿着一本速写册,你可能会想出另外50种让 Gopher 搬运书的方法。有不少不一样的设计,它们没必要都相同,但它们都能工做。而后,您能够对这些并发设计进行重构、从新排列、按不一样的维度进行缩放,获得不一样的功能以处理问题。真是太好了,由于无论你怎么作,处理这个问题的算法的正确性很容易保证。这样作不会搞砸,个人意思它们只是 Gopher,你知道这些设计本质上是安全的,由于你是那样作的。可是,这无疑是一个愚蠢的问题,与实际工做无关。嗯,事实上确实有关。git

由于若是你拿到这个问题,把书堆换成一些网络内容;把 Gopher 换成 CPU,把推车换成网络或编码代码等等;把问题变成你须要移动数据;焚化炉是网络代理,或是浏览器,你想到的任何的数据使用者。您刚刚构建了一个 Web 服务体系结构的设计。你可能不认为你的 Web 服务架构是这样的,但事实上差很少就是这样。你能够把这两块替换掉看看,这正是你想的那种设计。当你谈论代理、转发代理和缓冲区之类会,扩容更多的实例的东西时,它们都在这个图上,只是不被这么想。本质上并不难理解它们,Gopher 能作到,咱们也能。

A little background about Go

如今让我来展现如何在使用Go构建东西时采用这些概念。我不打算在此次演讲中教你 Go,但愿大家有些人已经知道它,但愿你们在以后能去更多了解它。但我要教一点点 Go,但愿其余人也能像咱们同样融入其中。

Goroutines

Go 有叫作 goroutine 的东西,能够认为有点像线程,但其实是不一样的。与其详细地谈有什么不一样,不如说说它是什么吧。假设咱们有一个函数,函数有两个参数。若是在程序中调用该函数 F,则在执行下一条语句以前要等待该函数完成。很熟悉,你们都知道。可是,若是在调用该函数以前放置关键字 go。你调用该函数,函数开始运行,虽然不一,至少在概念上能够当即继续运行。想一想并发与并行,从概念上讲,当 F 不在时,程序一直在运行,你在作 F 的事情,不用等 F 回来。若是你以为很困惑,那就把它想象成一个很像 shell 里的 & 符号。这就像在后台运行 F &,确切地说是一个 goroutine。

它有点像线程,由于一块儿运行在同一个地址空间中,至少在一个程序中如此。但 goroutine 要廉价得多,建立很容易,建立成本也很低。而后根据须要,goroutine 动态地多路映射到执行中的操做系统线程上,因此没必要担忧调度、阻塞等等,系统会帮你处理。当 goroutine 确实须要阻塞执行像 read 之类的系统调用时,其余 goroutine 不须要等待它,它们都是动态调度的。因此 goroutine 感受像线程,倒是更轻量版本的线程。这不是一个原始的概念,其余语言和系统已经实现了相似的东西。咱们给它起了本身的名字来讲明它是什么。因此称之为 goroutine。

Channels

刚刚已经提到须要在 goroutine 之间通讯。为了作到这一点,在 Go 中,称之为 channel。它有点像 shell 中的管道,但它有类型,还有其余一些很棒的特性,今天就不深刻了。但如下有一个至关小的例子。咱们建立了一个 timer channel,显然它是一个时间值的 channel;而后在后台启动这个函数;sleep 必定的时间 deltaT,而后在 timer channel 上发送当时的时间 time.now()。由于此函数是用 go 语句启动的,不须要等待它。它能够作任何想作的事情,当须要知道其余 goroutine 完成时,它说我想从 timer channel 接收那个值。该 goroutine 会阻塞直到有一个值被传递过来。一旦完成,它将设置为获得的时间,即其余 goroutine 完成的时间。小例子,但你须要的一切都在那张小幻灯片里。

Select

最后一部分叫作 select。它让你能够经过同时监听多个 channel 控制程序的行为。一旦就能看出谁准备好通讯了,你就能够读取。在这种状况下, channel 1channel 2,程序的行为将不一样,取决于 channel 1channel 2 是否准备就绪。在这种状况下,若是两个都没有准备好,那么 default 子句将运行,这意味着,若是没有人准备好进行通讯,那么你会 fall through。若是 default 子句不存在,执行 select,须要等待其中一个或另外一个 channel 就绪。若是它们都准备好了,系统会随机挑选一个。因此这种要晚一点才能结束。像 switch 语句,但用于通讯场景。若是你知道 Dijkstra 的监督命令,应该会很熟悉

当我说 Go 支持并发,是说它确实支持并发,在 Go 程序中建立数千个 goroutine 是常规操做。咱们曾经在会议现场调试一个go程序,它运行在生产环境,已经建立了130万个 goroutine,而且在调试它的时候,有1万个活跃的。固然,要作到如此,goroutine 必须比线程廉价得多,这是重点。goroutine 不是免费的,涉及到内存分配,但很少。它们根据须要增加和缩小,并且管理得很好。它们很是廉价,你能够认为和 Gopher 同样廉价。

Closures

你还须要闭包,我刚在前面的页面展现过闭包,这只是在 Go 语言中可使用它的证据。由于它们是很是方便的并发表达式,能够建立匿名的 procedure。所以,您能够建立一个函数,在本例中,组合多个函数返回一个函数。这只是一个有效的证实,它是真正的闭包,可使用 go 语句运行。

让咱们使用这些元素来构建一些示例。我但愿你能潜移默化的学习一些 Go 并发编程,这是最好的学习方法。

Some examples

Launching daemons

从启动一个守护进程开始,您可使用闭包来包装一些您但愿完成但不想等待的后台操做。在这种状况下,咱们有两个 channel 输入和输出,不管出于什么缘由,咱们须要将输入传递到输出,但不想等到复制完成。因此咱们使用 go func 和 闭包,而后有一个 for 循环,它读取输入值并写入输出,Go 中的 for range 子句将耗尽 channel。它一直运行直到 channel 为空,而后退出。因此这一小段代码会自动耗尽 channel。由于在后台运行,因此你不须要等待它。这是一个小小的范例,但你知道它还不错,并且已经习惯了。

A simple load balancer

如今让我向您展现一个很是简单的 Load Balancer。若是有时间的话,我会给你看另外一个例子。这个例子很简单,想象一下你有一大堆工做要完成。咱们将它们抽象出来,将它们具体化为一个包含三个整数值的 Work 结构体,您须要对其执行一些操做。

worker 要作的是根据这些值执行一些计算。而后我在此处加入 Sleep,以保证咱们考虑阻塞。由于 worker 可能会被阻塞的必定的时间。咱们构造它的方式是让 worker 从 input channel 读取要作的工做,并经过 output channel 传递结果,它们是这个函数的参数。在循环中,遍历输入值,执行计算,sleep 一段任意长的时间,而后将响应传递给输出,传递给等待的任务,因此咱们得操心阻塞。那必定很难,对吧,如下就是所有的解决方案。

之因此如此简单,是由于channel 以及它与语言的其余元素一块儿工做的方式,让您可以表达并发的东西,并很好地组合它们。作法是建立两个 channel, input channel 和 output channel,链接到 worker。 全部 worker 从 input channel 读取,而后传输到 output channel;而后启动任意数量的 worker。注意中间的 go 子句,全部 worker 都在并发运行,也许是并行运行;而后你开始另外一项工做,如屏幕显示为这些 worker 创造大量的工做,而后在函数调用中挂起,接收大量的结果,即从 ouput channel 中按照结果完成的顺序读取其值。由于做业结构化的方式,无论是在一个处理器上运行仍是在一千个处理器上运行,都会正确而完整地运行。任何人使用该资源均可以一样完成,系统已经为你作好了一切。若是你思考这个问题,它很微不足道。但实际上,在大多数语言中,若是没有并发性,很难简洁地编写。并发性使得作这种事情,能够很是紧凑。

更重要的是,它是隐式并行性的(尽管不是,若是你不想,能够没必要考虑该问题),它也能很好地扩展。没有同步或不一样步。 worker 数量多是一个巨大的数字,并且它仍然能够高效地工做,所以并发工具使得为较大问题构建此类解决方案变得很容易。

还要注意,没有锁了,没有互斥锁了,全部这些都是在考虑旧的并发模型时须要考虑的,新模型没有了,你看不到它们的存在。然而,一个正确的无锁的并发、并行算法,必定很好,对吧?

但这太容易了,咱们有时间看一个更难的。

Load balancer

此例子有点棘手,相同的基本概念,但作的事情更符合现实。假设咱们要写一个 Loader Balancer,有一堆 Requester 生成实际的工做,有一堆工做任务。但愿将全部这些 Requester 的工做负载分配给任意数量的 Worker,并使其达到某种负载平衡,因此工做会分配给负荷最少的 Worker。 因此你能够认为 Worker 们一次可能有大量的工做要作。他们可能同时要作的不止一个,可能有不少。由于有不少请求在处理,因此这会是一个很忙碌的系统。正如我演示的同样,它们也许是在同一台机器上。您也能够想象,其中一些线表明了正在进行适当负载均衡的网络链接,从结构上讲,咱们的设计仍然是安全的。

Request 如今看起来很不同了。有一个任意数量函数的闭包,表示咱们要作的计算;有一个 channel 能够返回结果。请注意,不像其余一些相似 Erlang 的语言,在 Go 中 channel 是 Reuqest 的一部分,channel 的概念就在那里,它是语言中 first-class 的东西,使得能够处处传递 channel。它在某种意义上相似于文件描述符,持有 channel 的对象就能够和其余对象通讯,但没有 channel 的对象是作不到的。就好像打电话给别人,或者经过文件描述符传递文件同样,是一个至关有影响的概念。想法是,要发送一个须要计算的请求,它包含一个计算完成返回结果的 channel。

如下是一个虚构但能起到说明做用的版本的 Requester。所作的是,有一个请求能够进入的 channel,在这个 work channel 上生成要作的要作的任务;建立了一个 channel,并放入每一个请求的内部,以便返回给咱们答案。作了一些工做,使用 Sleep 表明(谁知道实际上在作什么)。你在 work channel 上发送一个带有用于计算的函数的请求对象,无论是什么,我不在意;还有一个把答案发回去的 channel;而后你在那个 channel 等待结果返回。一旦你获得结果,你可能得对结果作些什么。这段代码只是按照必定速度生成工做。它只是产生结果,可是经过使用 input 和 output channel 通讯来实现的。

而后是 Worker,在前面的页面,记得么?有一些 Requester,右边的是 Worker,它被提供给 balancer,是我最后要给你看的。 Worker 拥有一个接收请求的 channel;一个等待任务的计数, Worker 拥有任务的数量表明其负载,它注定很忙;而后是一个 index,是堆结构的一部分,咱们立刻展现给你看。 Worker 所作的就是从它的 Requester 那里接收工做。 Request channel 是 Worker 对象的一部分。

调用 Worker 的函数,把请求传递给它,把从 Requester 生成的实际的函数经过均衡器传递给 WorkerWorker 计算答案,而后在 channel 上返回答案。请注意,与许多其余负载均衡架构不一样,从 Worker 返回给 Requester 的 channel 不经过 Loader Balancer。一旦 RequesterWorker 创建链接,图中的“中介”就会消失,请求上的工做直接进行通讯。由于在系统运行时,系统内部能够来回传递 channel。若是愿意,也能够在里面放一个 goroutine,在这里放一个 go 语句,而后在 Worker 上并行地处理全部的请求。若是这样作的话,同样会工做的很好,但这已经足够了。

Balancer 有点神奇,你须要一个 Workerpool; 须要一些 Balancer 对象,以绑定一些方法到 BalancerBalancer 包含一个 pool;一个 done channel,用以让 Worker 告诉 Loader Balancer 它已经完成了最近的计算。

因此 balance 很简单,它所作的只是永远执行一个 select 语句,等待作更多来自 Requester 的工做。在这种状况下,它会分发请求给负载最轻的 Worker;或者 Worker 告知,它已经完成计算,在这种状况下,能够经过更新数据结构代表 Worker 完成了它的任务。因此这只是一个简单的两路 select。而后,咱们须要增长这两个函数,而要作到这一点,实际上要作的就是构造一个堆。

我跳过这些使人很兴奋的片断,大家已经知道什么意思。

Dispatch, dispatch 要作的就是找到负载最少的 Worker,它是基于堆实现的一个标准优先级队列。因此你把负载最少的 Worker 从堆里取出来,经过将请求写入 request channel 来发送任务。由于增长了一个任务,须要增长负载,这会影响负载分布。而后你把它放回堆的同一个地方,就这样。你刚刚调度了它,而且在结构上进行了更新,这就是可执行代码行的内容。

而后是 complete 的任务,也就是工做完成后,必须作一些相反的事情。 Worker 的队列中减小了一个任务,因此减小了它的等待计数。从堆里弹出 Worker,而后把它放回堆中,优先级队列会把它放回中它所属的位置,这是一个半现实的 Loader Balancer 的完整实现。此处的关键点是数据结构使用的是 channel 和 goroutine 来构造并发的东西。

Lesson

结果是可伸缩的,是正确的,很简单,没有显式的锁,而架构让它得以实现。所以,并发性使此例子的内在并行性成为可能。你能够运行这个程序,我有这个程序,它是可编译、可运行的,并且工做正常,负载均衡也作得很好。物体保持在均匀的负载下,按照模块量化,很不错。我历来没说过有多少 Worker,有多少问题。可能每一个都有一个,另外一个有数10个;或者每一个都有一千,或者每一个都有一百万,扩缩容仍然有效,而且仍然高效。

One more example

再举一个例子,这个例子有点使人惊讶,但它适合一张幻灯片就能够完成。

想象一下如何复制数据库,你获得了几个数据库,每一个数据库中有相同的数据,谷歌称之为分片,称呼相同的实例。您要作的是向全部数据库传递一个请求,一个查询,并返回结果。结果会是同样的,你选择第一个应答请求来加快速度,由于首先要回来的是你想要的答案。若是其中一个坏了,断开了或者什么的,你不在意。由于会有其余响应回来,这就是如何作到这一点。这就是它的所有实现。您有一些链接数组和一些要执行的查询,您建立一个 channel,该 channel 缓冲查询数据库中的元素数、副本内的副本数大小的内容,而后您只需在数据库的全部链接上执行。对于其中的每个,您启动一个 goroutine 以将查询传递到该数据库,而后获取答案。可是经过这个 DoQuery 调用,将答案传递到惟一的 channel,这个 channel 保存全部请求的结果。而后,在你执行以后,全部的 goroutine 都只需在底部这行等待。咱们等待第一个回到 channel 的请求,就是你想要的答案。返回它,就完成了。这看起来像个玩具,并且有点像。但这其实是一个彻底正确的实现,惟一缺乏的是干净的回收。你想告诉那些还没回来的服务器关闭。当你已经获得答案,再也不须要它们。你能够作,增长更多且合理的代码,但那就不适合放在幻灯片上了。因此我只想告诉你,在不少系统中,这是一个至关复杂的问题,但在这里,它只是天然地脱离了架构。由于你已经有了并发工具来表示一个至关大的复杂的分布式问题,它运行得很是好。

Conclusion

还剩五秒钟,很好。结论:并发性是强大的,但它不是并行性的,但它支持并行性,并且它使并行性变得容易。若是你明白了,那我就完成了个人工做。

For more information

若是你想看更多,这里有不少连接。 golang.org 有关于 GO 你想知道的一切。有一份很好的历史 paper,连接如上。几年前我作了一个演讲,让咱们真正开始开发Go语言,你可能会以为颇有趣。CMU 的 Bob Harper 有一篇很是不错的博客文章,叫作“并行不是并发”,这与“并发不是并行”的观点很是类似,虽然不彻底同样。还有一些其余的东西,最使人惊讶的是,道格·马图尔(Doug Mathur),我在贝尔实验室(Bell Labs)的老板,所作的并行幂级数的工做,这是一篇了不得的论文。但若是你想不同凡响的话。幻灯片上的最后一个连接是到另外一种语言 sawzall,我从贝尔实验室(Bell Labs)来到谷歌后不久作的,这很了不得,由于它是难以想象的并行的语言,但它绝对没有并发性。如今我想你可能明白了这是可能的,因此很是感谢你的倾听和感谢 Hiroko 给我写信。我想是时候喝点什么了。

视频:vimeo.com/49718712
Slide:talks.golang.org/2012/waza.s…
源代码:github.com/golang/talk…程序员

相关文章
相关标签/搜索