为什么协程又回来了?[关闭]


19

协程的大部分基础工作都发生在60年代/​​ 70年代,然后停下来,取而代之的是其他选择(例如线程)

对以Python和其他语言出现的协程产生新兴趣的实质是什么?



9
我不确定他们是否离开过。
Blrfl

Answers:


26

协程从未离开过,与此同时,它们只是被其他事物所笼罩。最近对异步编程以及协程的兴趣增加,主要是由于以下三个因素:功能编程技术的接受程度提高,对真正的并行性支持不佳的工具集(JavaScript!Python!),最重要的是:线程和协程之间的权衡取舍。对于某些用例,协程在客观上更好。

OOP是80年代,90年代乃至当今最大的编程范例之一。如果我们看一下OOP的历史,特别是Simula语言的发展,就会发现类是从协程演变而来的。Simula用于模拟具有离散事件的系统。系统的每个元素都是一个单独的流程,它将在一个模拟步骤的持续时间内响应事件而执行,然后屈服于让其他流程执行其工作。在Simula 67的开发过程中,引入了类概念。现在,协程的持久状态存储在对象成员中,并且通过调用方法来触发事件。有关更多详细信息,请考虑阅读Nygaard&Dahl 撰写的论文《 SIMULA语言的发展》

因此,有趣的是,我们一直在使用协程,我们只是将它们称为对象和事件驱动的编程。

关于并行性,有两种语言:具有适当内存模型的语言和没有适当语言的语言。内存模型讨论诸如“如果我写一个变量,然后在另一个线程中从该变量中读取,我会看到旧值或新值还是无效值吗?“之前”和“之后”是什么意思?保证哪些操作是原子操作?”

创建一个好的内存模型很困难,因此对于大多数未指定的,实现定义的动态开源语言(Perl,JavaScript,Python,Ruby,PHP),从来没有做过这样的工作。当然,所有这些语言的发展都远远超出了最初为它们编写的“脚本”。好的,其中一些语言确实具有某种内存模型文档,但是还不够。相反,我们有一些技巧:

  • Perl可以在线程支持下进行编译,但是每个线程都包含一个完整的解释器状态的单独克隆,这使线程变得非常昂贵。唯一的好处是,这种无共享方法避免了数据争用,并迫使程序员仅通过队列/信号/ IPC进行通信。Perl对于异步处理没有很强的故事。

  • JavaScript一直对函数式编程提供了丰富的支持,因此程序员可以在需要异步操作的程序中手动编码连续性/回调。例如,对于Ajax请求或动画延迟。由于Web本质上是异步的,因此有很多异步JavaScript代码,并且管理所有这些回调非常痛苦。因此,我们看到了许多努力来更好地组织这些回调(Promise)或完全消除它们。

  • Python具有这种不幸的功能,称为全局解释器锁定。基本上,Python内存模型是“由于没有并行性,所有效果都会顺序出现。因此,虽然Python确实有线程,但它们仅与协程一样强大。[1] Python可以通过带有的生成器函数对许多协程进行编码yield。如果使用得当,则仅此一项就可以避免JavaScript已知的大多数回调地狱。Python 3.5中更新的async / await系统使异步惯用语在Python中更加方便,并集成了事件循环。

    [1]:从技术上讲,这些限制仅适用于CPython(Python参考实现)。其他类似Jython的实现确实提供了可以并行执行的真实线程,但是必须花很长时间才能实现等效的行为。本质上:每个变量或对象成员都是易失性变量,因此所有更改都是原子性的,可以在所有线程中立即看到。当然,使用易变变量比使用普通变量要昂贵得多。

  • 我对Ruby和PHP知之甚少,无法正确烘焙它们。

总结一下:这些语言中的某些具有基本的设计决策,这些决策使多线程不受欢迎或无法实现,从而导致人们更加关注协程等替代方案以及使异步编程更方便的方法。

最后,让我们谈谈协程和线程之间的区别:

线程基本上类似于进程,除了进程内的多个线程共享一个内存空间。这意味着线程在内存方面绝不是“轻量级”。线程由操作系统抢先调度。这意味着任务切换具有高开销,并且可能会在不方便的时间发生。此开销包括两个部分:挂起线程状态的开销,以及在用户模式(对于线程)和内核模式(对于调度程序)之间切换的开销。

如果一个进程直接和协作地调度其自己的线程,则无需将上下文切换到内核模式,并且与间接函数调用相比,切换任务的成本相对较高,例如:相当便宜。根据各种细节,这些轻量级的线可以称为生线,纤维或协程。绿色线程/光纤的著名用户是早期的Java实现,而最近在Golang中使用Goroutines。协程的概念优势在于,可以根据在协程之间明确传递的控制流来理解它们的执行。但是,除非在多个OS线程之间调度这些协程,否则它们不会实现真正的并行性。

便宜的协程在哪里有用?大多数软件不需要大量的线程,因此通常昂贵的线程通常是可以的。但是,异步编程有时可以简化您的代码。要自由使用,此抽象必须足够便宜。

然后是网络。如上所述,Web本质上是异步的。网络请求只需要很长时间。许多Web服务器维护一个充满工作线程的线程池。但是,在大多数情况下,这些线程将处于空闲状态,因为它们正在等待某些资源,例如从磁盘加载文件时等待I / O事件,等待客户端确认响应的一部分或等待数据库查询完成。NodeJS惊人地证明了基于事件的异步服务器设计非常有效。显然,JavaScript并不是唯一一种用于Web应用程序的语言,因此,其他语言(在Python和C#中值得注意)也极大地推动了异步Web编程的发展。


我建议您在第四段至最后一段进行搜索,以免遭受risk窃的风险,这几乎与我阅读的其他资料完全相同。另外,尽管开销比线程小几个数量级,但协程的性能不能简化为“间接函数调用”。在此处此处,请参阅Boosts有关协程实现的详细信息。
WHN

1
@snb关于建议的编辑:GIL可能是CPython实现的细节,但是根本的问题是Python 语言没有显式的内存模型来指定数据的并行变异。GIL是回避这些问题的黑客。但是具有真正的并行性的Python实现必须经过相当长的时间才能提供等效的语义,例如Jython书中所讨论的。基本上:每个变量或对象字段都必须是昂贵的volatile变量。
阿蒙

3
@snb关于窃:Pla窃是错误地将想法表达为您自己的想法,尤其是在学术背景下。这是一个严重的指控,但我敢肯定您不是那样的意思。“线程基本上就像进程”一节仅重申了众所周知的事实,正如有关操作系统的任何讲座或教科书中所教导的那样。由于只有很多种方法可以简明扼要地表述这些事实,因此您对本段听起来很熟悉并不感到惊讶。
阿蒙

我没有改变含义以暗示Python 确实具有内存模型。同样,使用volatile本身并不能降低性能 volatile只是意味着编译器无法以可以假设变量在当前上下文中没有显式操作不变的方式优化变量。在Jython世界中,这实际上可能很重要,因为它将使用VM JIT编译,但是在CPython世界中,您不必担心JIT优化,您的易失变量将存在于解释器运行时空间中,无法进行优化。
WHN

7

协程过去非常有用,因为操作系统没有执行抢先式调度。一旦他们开始提供抢先式调度,则不再需要定期放弃程序中的控制。

随着多核处理器变得越来越普遍,协程被用于实现任务并行性和/或保持较高的系统利用率(当一个执行线程必须等待资源时,另一个线程可以开始在其位置运行)。

NodeJS是一种特殊情况,使用协程可以并行访问IO。即,多个线程用于服务IO请求,但单个线程用于执行javascript代码。在信号线程中执行用户代码的目的是避免使用互斥锁。如上所述,这属于试图保持系统利用率高的类别。


4
但是协程不是由OS管理的。操作系统不知道协程是什么,不像C ++光纤
过度交换

许多操作系统都有协程。
约尔格W¯¯米塔格

像python和Javascript ES6 +这样的协程不是多进程的吗?它们如何实现任务并行性?
WHN

1
@Mael协程的最新“复兴”来自python和javascript,据我所知,它们都无法与协程实现并行性。就是说这个答案是错误的,因为任务并行性根本不是协程“退缩”的原因。 Luas也不是多进程的吗?编辑:我刚刚意识到您不是在谈论并行性,但是为什么您首先回复我?回答dlasalle,因为显然他们对此是错误的。
WHN

3
@dlasalle不,尽管它说“并行运行”并不意味着任何代码都在物理上同时运行,但他们不能这样做。GIL会停止它,并且异步不会产生CPython中多处理所需的单独进程(单独的GIL)。异步在单个线程上处理yield。当他们说“parralel”实际上他们的意思多种功能yeilding的其他功能的工作和interleving函数执行。由于impl,Python异步进程无法并行运行。我现在有三种不做并行处理的语言,Lua,Javascript和Python。
WHN

5

早期的系统使用协程提供并发,主要是因为它们是最简单的方式。线程需要操作系统的大量支持(您可以在用户级别上实现它们,但是您将需要某种方式安排系统定期中断您的进程),并且即使您确实有支持,也很难实现。

线程后来开始接管,因为在70或80年代,所有严肃的操作系统都支持它们(到90年代,甚至是Windows!),并且它们变得更加通用。而且它们更易于使用。突然,每个人都认为线程是下一件大事。

到90年代后期,裂缝开始出现,并且在2000年代初期,线程的严重问题变得显而易见:

  1. 他们消耗很多资源
  2. 相对而言,上下文切换会花费大量时间,并且通常是不必要的
  3. 他们破坏了参考地
  4. 编写正确的代码来协调可能需要互斥访问的多个资源是出乎意料的困难

随着时间的流逝,程序通常需要在任何时候执行的任务数量迅速增长,从而增加了由上述(1)和(2)引起的问题。处理器速度和内存访问时间之间的差距一直在增加,这加剧了问题(3)。而且,就程序所需的资源数量和种类的不同而言,程序的复杂性正在增长,从而增加了问题的相关性(4)。

但是,通过失去一点通用性,并给程序员增加一些额外的负担来考虑他们的流程如何一起运行,协程可以解决所有这些问题。

  1. 协程比少数页面需要更多的资源,比大多数线程实现要少得多。
  2. 协程仅在程序员定义的点切换上下文,这希望仅在必要时进行。它们通常也不需要像线程那样保留尽可能多的上下文信息(例如,寄存器值),这意味着每个开关通常都更快,并且需要的次数更少。
  3. 包括生产者/消费者类型操作在内的常见协程模式以主动增加局部性的方式在例程之间传递数据。此外,上下文切换通常仅发生在工作单元之间而不是它们内部,即通常无论如何都将局部性最小化的时间。
  4. 当例程知道在操作过程中不能任意中断资源时,则不太可能需要资源锁定,从而使更简单的实现正常工作。

5

前言

首先,我想说明一下协程复活的原因,即并行性。通常,现代协程不是实现基于任务的并行性的手段,因为现代实现不利用多处理功能。最接近的是纤维

现代用法(为什么又回来了)

现代协程已经成为实现惰性评估的一种方式,这在诸如haskell之类的功能语言中非常有用,在这种语言中,通过遍历整个集合而不是遍历整个集合来执行操作,您可以根据需要执行仅评估的操作(适用于项目的无限集或具有提前终止和子集的其他大型集)。

通过使用Yield关键字在Python和C#等语言中创建生成器(它们本身可以满足部分懒惰的评估需求),现代实现中的协程不仅是可能的,而且在语言本身没有特殊语法的情况下也是可能的(尽管python最终增加了一些帮助)。协同例程可以通过future s的概念来帮助实现懒惰的评估,如果将来您不需要变量的值,则可以延迟实际获取它的时间,直到您明确要求该值为止(允许您使用该值和懒惰地在与实例化不同的时间评估它)。

但是,除了懒惰求值之外,尤其是在Web领域,这些协同例程还有助于修复回调地狱。协程在数据库访问,在线事务,用户界面等方面变得很有用,在客户端机器上的处理时间不会导致更快地访问所需内容。线程可以完全填满同一件事,但是在这个领域需要更多的开销,并且与协程相反,实际上对于任务并行性很有用。

简而言之,随着Web开发的发展和功能范例与命令式语言的融合越来越多,协程已成为异步问题和惰性评估的解决方案。协程出现在问题空间中,在这里,多进程线程和一般情况下的线程是不必要,不便或不可能的。

现代例子

在如JavaScript,Lua中,C#和Python语言协同程序都是由各个功能获得其实现放弃主线程等功能(无关的操作系统调用)的控制。

这个python示例中,我们有一个有趣的python函数,其中包含一些await内部函数。这基本上是一个收益,它将收益产生到loop,然后允许运行不同的功能(在这种情况下,运行不同的factorial功能)。请注意,当它说“并行执行任务”时,它实际上并没有并行执行,而是通过使用await关键字来执行其交织功能(请记住,这只是一种特殊的收益类型)

它们允许单个非平行,控制产量的并发处理这不是任务并行,在这个意义上,这些任务不工作永远在同一时间。协程不是现代语言实现中的线程。协例程的所有这些语言实现都是从这些函数yield调用派生的(程序员必须实际将其手动放入协例程中)。

编辑:C ++ Boost coroutine2以相同的方式工作,并且他们的解释应该更好地了解我在谈论yeilds,请参阅此处。如您所见,实现没有“特殊情况”,增强纤维之类的东西是该规则的例外,甚至需要显式同步。

EDIT2:因为有人认为我在谈论基于c#任务的系统,所以我不是。我在谈论Unity的系统和朴素的C#实现


@ T.Sar我从来没有说过C#有任何“自然的”协程,C ++(可能会改变)也没有python(并且仍然有它们),并且这三个都有协同例程实现。但是协程的所有C#实现(如统一的协程)都基于我描述的收益。另外,您在此处使用“ hack”是没有意义的,我想每个程序都是hack,因为它并非总是用该语言定义的。我绝不会将C#“基于任务的系统”与任何东西混合在一起,我什至没有提到。
WHN

我建议使您的答案更加清晰。C#既有等待指令的概念,又有基于任务的并行系统-使用C#和这些单词同时在python上提供有关python并非真正并行的示例,可能会引起很多困难。另外,删除您的第一句话-不需要直接用这样的答案攻击其他用户。
T. Sar-恢复莫妮卡
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.