七叶笔记 » golang编程 » Python并发处理:CSP和协程

Python并发处理:CSP和协程

概述

通信顺序进程(Communicating sequential processes,称CSP)的概念与Actor模型类似,但他为当今的并发挑战带来了新的应对工具。这两种模型都利用了消息传递机制,但是Actor模型在状态容器之间传递消息,CSP模型则在通道之间传递消息,这种通道是协程或类协程程序之间的同步或通信形式。

拉斯·考克斯(RussCox)撰写了大量有关CSP历史的文章。

这种并发模式解决了在生产中实现并发的许多困难:

高效地抽象出对底层硬件的依赖性

物理硬件(如CPU或者I/O)的阻塞操作固然十分耗费资源,然而设计一个完整的虚拟机来抽象出硬件上的依赖(如BEAMVM上的Erlang进程)却更加昂贵,这就使得找到一种代码紧贴硬件,亦可实现将阻塞的动作从硬件语句中抽象出来的成本低廉而又简单易用的方式变得十分重要。CSP通过在CPU 线程 池之上复用和调度协程来提供此功能,并在语言线程/进程与硬件线程/进程之间建立多对多关系。由于调度程序只是状态机,因此可以将其编译为二进制文件并在任何位置加载,而无需进行复杂的配置。

例如,golang协程或“goroutine”可以便宜到在只有几千字节的内存中启动。在golang常被提到的问题中提到了每个goroutine中的3个CPU指令的平均开销。对于考虑在线程和线程锁上使用CSP的公司,或在性能优先的其他场合使用并发模型,这可以提高收益/成本比例。

增加用户采用率

关注通道而不是应用程序代码,不仅为新程序员提供了一种更容易的方法,使他们可以分两部将语言和并发模型组织起来,而且还减少了在“工作—>正确—>快速”阶段将使用该语言编写的旧代码转换为使用首选的并发模型所带来的痛苦。actor模型迫使语言用户从一开始就将其容器化并打破其状态以适应并发模型,这与面向对象的编程相比是一个巨大的转变。调度程序能够在阻止程序时简单地暂停协程,并在取消阻塞并提供机器线程后恢复执行协程的能力,将对应用程序状态的控制权返回给用户。这允许在特定协程中进行无界的状态突变(只要状态位于协程中),这是许多程序员比在消息中包装状态更熟悉的概念,并且可以更好地对有状态的顺序问题进行建模。

在以下的情况下,你可能不想使用CSP:

协程需要跨渠道共享大量数据

Ravelin在golang通道上共享了一些信息,这表明与将数据保存在一组goroutine中相比,跨通道的goroutine之间共享数据的开销很大。CSP通道本质上也是同步的,如果将与CPU绑定的任务保留在goroutine中,并且goroutine通过I/O与资源进行通信,则吞吐量可能会更好。SylvainWallez在golang的失败方面也写了一篇很棒的博客,其中描述了golang提供的数据结构不是并发优先(例如可变,非原子)时跨通道共享数据结构和内存的困难等,这可能会造成不小的调试开销。

目前,在编程语言中,最流行的CSP示例是golang,其并发模型是围绕Hoare的CSP过程演算而设计的,并以一流的公民身份实现渠道。

您可以在golang/src/runtime/proc.go中看到实现的CSP调度程序。

PaulButcher在Clojure的core.async上下文中讨论了CSP,Clojure是一个库。RichHickey在此Clojure版本的文章中介绍了core.async。

Python CSP库

Python本身不以一流公民的身份支持CSP或渠道。但是,许多小型学术项目提供了一个基础层,可以从中产生CSP框架:

  • MPI 4py :基于C/C++的消息传递接口或MPI的Python绑定。也存在许多其他支持MPI的Python绑定的框架,包括Boost的本机绑定。在MPI之上构造CSP抽象层的研究已经完成,但至少从参考文献上看,它似乎还不能投入生产。

  • python-csp :一个试图通过利用操作符重载来实现CSP流程演算的库,该库建立在multiprocessing模块之上。

例如,为了并行化多个CSPProcess对象,python-csp使用Par对象覆盖CSPProcess对象的floordiv操作:

文档中有关并行进程的示例如下:

由于缺乏持续的开发,缺乏功能的稳定性,不完整的文档以及对python-csp可能失败的常见方式的描述(没有错误模型的描述),因此不建议在生产中使用。

pycsp一个CSP框架,具有基于π演算的某些扩展。从粗略的检查来看,该框架似乎已被很好地记录和构建,并带有大量示例可供检查和运行。与基于Python的其他并发库相比的一个主要缺点是,未解决的问题数量和解决问题的平均时间表明缺乏用户采用率。

PythonCSP 原语

与其他缺乏Python基础的并发模型不同,可以通过使用Python的本机和各种协程以及协程库以Python方式完成CSP流程演算的实现。

Python的async库

Python协程的发展始于认识到生成器表达式如何与关键字 yield from和.send结合使用,导致相同的控制反转,从而可以同时调度单独的任务。AbuAshrafMasnun撰写了一篇有关此进化过程的博客。

PEP492在Python3.5.x中的实现提供了与生成程序协程分离的真正本地协程,具有专门针对性的异步/等待语法,异步上下文管理以及对inspectandsys的标准库支持(以及其他功能)。

许多库都在竞相利用这种本机支持,这说明了如果编程模型被Python标准库所接受,能够有效地提高用户采用率。

无堆栈Python, greenlet和gevent

StacklessPython是CPython的一个分支,它支持其他并发原语。与CSP讨论相关的任务包括小任务和频道。两者都很好地符合了CSP的协程和渠道构想。

greenlet是StacklessPython的tasklet概念的发展,除了调度和其他控制流原语是在用户代码中处理的。Tasklets是微线程的一种形式,而greenlets是协程的一种形式。Guido在邮件列表档案中讨论了两者之间的区别:

微线程是具有循环调度的“几乎真实的”线程。使它们对某些人有吸引力的原因是它们不使用操作系统资源:没有与之关联的OS级堆栈或内核进程表条目。这也解释了它们的最大弱点:当微线程挂起进行I/O时,所有微线程都挂起。

在有限的上下文中,例如网络服务器,可以通过类似于Gordon的SelectDispatcher中的方法来解决。(对每个可能阻塞的I/O操作执行此操作将是一项巨大的工作,并且可能不适用于C扩展。)

gevent 将greenlet库与libev事件循环库结合在一起,并提供Python绑定。在gevent之上构建了一些有趣的Python库,例如locust (HTTP负载测试框架)和gunicorn(Web服务器框架)。

实验/进行中的CSP计划

正在进行一些实验性的,特定于Python的并发计划。如果它们达到了稳定的发布点,则有一天它们可能会成为Python中CSP的基础(以及其他类型的并发模型)。

子解释器

埃里克·斯诺(EricSnow)开放了PEP525,该计划描述了在主Python解释器中利用子解释器的计划,该计划将所有Python的全局范围(包括C扩展API)在Python进程空间中下移一级。这可能有助于Python在语言中本地支持编排层,而不是使用第三方编排工具来协调多个不同的Python服务。埃里克(Eric)明确提到CSP是子解释器如何相互通信的一种启发:

并发是软件开发中具有挑战性的领域。数十年的研究和实践导致了各种各样的并发模型,每个模型都有不同的目标。大多数都集中在正确性和可用性上。

一类并发模型专注于通过某些消息传递方案进行互操作的隔离执行线程。一个显着的例子是通信顺序过程(CSP)(Go的并发大致基于此)。子解释器固有的隔离性使其非常适合此方法。

pypy

pypy是StacklessPython的演进,它结合了可插入垃圾回收器,即时编译器以及与大多数现有CPython代码以及greenlet的兼容性,以使本机Python更具性能。pypy在“continulet”之上实现了greenlet,而在“stacklet”之上进一步实现了greenlet,此工作是使得上下文切换具备了可组合和确定性。

用Pycsp实现Eratosthenessieve并发筛

Eratosthenes的Sieve是一个流行的计算机科学问题,它通过标记现有素数的所有倍数来计算有界序列中的素数。

golang实施了一种整洁的算法,可以使用通道来计算顺序素数;此示例可在golang playground 中运行。该算法的总体要旨是让一个通道创建一个顺序递增值的流,并以菊花链方式将其他通道链接到该流以丢弃倍数的值。菊花链的输出是序列中的质数。但是,正如StianEikeland在他的博客中使用Clojure的core.async指出的那样,以及正如本HackerNews讨论所假定的那样,此并发演示主要是学术性的,因为每个通道都涉及到每个主要因素,因此并发演示的效果不是很好在其最大素分解达到最大值之前。

pycsp在其网站上托管了此筛查实现的版本,但是在HEAD/fb9e32fd8aa88a33acce40d31aafcfe6693f0fff签出的pycsp开发版本上测试了提供的算法失败。手动修补套接字处理后,可以显式设置字节类型:

上图所示,成功将2到2000之间的质数记录到stdout。

结论

Python在本机实现CSP而言具有重要的限制。就揭露 golang和Python在语法和类型上如何区分进程间通信的优先级而言,这种Google网上论坛的讨论相当令人大开眼界。同时,与其他并发模型相比,CSP的灵活性使其更易于在本身不支持它的语言上实现。尽管Python中的CSP仍是一个学术性的讨论,但Python子解释器的稳定版本或async Python库上的可用于生产的CSP可能使有关生产Python环境中类似CSP并发的讨论变得更有意义。

相关文章