并发与 actor

并发与并行的区别

并发与并行这两个概念常常有人会搞错。并发是问题域中的概念——程序需要被设计成能够处理多个同时(或者几乎同时)发生的事件;而并行则是方法域中的概念——通过将问题中的多个部分并行执行,来加速解决问题的能力。

  • 并发是同一时间应对多件事情的能力。
  • 并行是同一时间做多件事情的能力。

Thread 和 Greenthread

Thread:

  • 系统内核态,更轻量的进程
  • 由系统内核进行调度
  • 同一进程的多个线程可共享资源

线程的出现解决了两个问题,一个是 GUI 出现后急切需要并发机制来保证用户界面的响应。第二是互联网应用发展后带来的多用户问题。但是线程的出现也额外带来了更多的复杂度,如静态条件、依赖关系和执行顺序等等,为了解决这些额外的复杂度,我们引入了更多的复杂机制来保证线程安全,如加锁保护数据、信号量同步信息等等方案。除了上面带来的这些复杂度之外,更让人头疼的问题还有线程的成本问题需要我们考虑,如线程的栈空间、调度成本、CPU使用率等等。从上述有关于线程的一些复杂度来说,我们可以得出一个结论:1.
线程的成本较高,不能大规模创建。2. 应该从语言或者框架层面解决这些问题。因此,人们相出了线程池的方案来解决问题。然而线程池的出现反而是我们能够更加‘方便’的滥用线程了…
除了线程池之外,还有没有更好的办法来利用线程呢?答案是有的。人们相出了其他两种方案。

  1. 异步回调方案。典型的例子是 Node.js
  2. GreenThread 方案。协程方案与异步回调方案其实本质上没多大区别,关键在于其独有的回调上下文的保存和执行机制。

GreenThread 相比 Thread 的优势在于:

  • 用户空间 首先是在用户空间,避免内核态和用户态的切换导致的成本。
  • 由语言或者框架层调度
  • 更小的栈空间允许创建大量实例(百万级别)

但是 GreenThread 还是有几个问题还没有解决:比如说 CPU 利用率问题, 资源瓶颈问题,‘协程池’里面放置多少协程合适等等。于是人们想出了用 Actor 模型来解决这些问题。

为什么用 Actor 模型?

Actor 其实跟面向对象理念中的 Object 想类似。面向对象中崇尚一切皆对象,Actor 模型中同样也有一切皆 Actor 的理念,两者都是一种抽象(虽然拿编程范式和并发模型来对比不太严谨)。面向对象实现的抽象是 对象 = 属性 + 行为(方法),当调用方调用对象行为的时候,占用的是调用方的 CPU 时间片,是否并发调用也是由调用方决定的,其实这是有悖于现实的。而 Actor 则相反,Actor 之间的消息通信都是通过异步的方式来进行的,actor
自身可以做计算,不需要占用调用方的 CPU 时间片,并发策略同样也是由 被调用方自身做决定的。因此 纯 GreenThread 中的 CPU 利用率、资源瓶颈、协程数量等问题也相应的被规避了。

比较知名的 Actor 的实现由 Erlang/OTP 、 Akka 等等。

Asyncio

Python Asyncio 的进化

因为有 GIL 的原因,Python 运行的时候无法真正地利用多线程并行,同一时刻下永远只有一个线程在真正地执行。因此不适合执行 CPU 计算密集的程序,线程之间的切换开销一直是 GIL 的瓶颈。直到后来 Python 引入了 GreenThread 的概念。Python 的 yield 生成器就是通过协程来实现的。但是有了生成器还不够,我们还希望能够给协程发送消息的功能,于是加入了 send
功能。能够给协程发送消息还不够,我们还需要重构生成器,于是加入了 yield from 用于重构生成器。光有这些简单的功能还不够,Python3 中加入了更为成熟的 asyncio 模块,使得协程逐渐独立于生成器之外。

aioactor demo