[关闭]
@spiritnotes 2016-03-02T16:46:16.000000Z 字数 3236 阅读 2451

并发之痛 Thead、Goroutine、Actor

博文笔记


来自于王渊命演讲

并发(concurrency)与并行(parallelism)

并发的关注点在于任务切分。多个任务看上去并行执行。并发不要求必须并行,可以用时间片切分的方式模拟。并发的要求是任务能切分成独立执行的片段。并行的关注点在于同时执行,必须是多(核)CPU。并发在乎结构,并行在乎执行。

并发程序为什么难

写正确的并发,容错,可扩展的程序如此之难,是因为我们使用了错误的工具和错误的抽象。

程序是面向过程的,数据结构+func,后来有了对象,组合了数据结构和func,我们用模拟世界的方式,抽象出对象,有状态和行为。无论是面向过程的func还是面向对象的func,本质上都是代码块的组织单元,本身并没有包含代码块的并发策略的定义。因此引入线程(Thread):

线程的出现,解决了GUI的响应以及互联网发展带来的多用户问题。
线程的使用比较简单,使用多少线程,什么时候使用线程由使用者决定,但是代码如何调用则由系统决定,因此容易产生不少问题,带来不少复杂度

引入不少复杂机制来保证正确:

系统到底需要多少线程?

线程的多少不好控制,使用不够,使用过多导致崩溃等从外部系统来观察或以经验的方式进行计算,都是比较困难的。

结论:线程的成本较高,不可能大规模创建;应该由语言或者框架动态解决这个问题

Java引入线程池,但是并没有完全解决问题

新思路

如果线程一直处于运行状态,只需设置和核数一样的线程数就可。

陈力就列,不能者止

两种方案:

GreenThread

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

几个概念

Goroutine

Goroutine是GreenThread系列解决方案的一种演进和实现

调度说明

Goroutine解决了CPU利用率的问题,其他的系统瓶颈(带锁的共享资源、数据库链接等)。如果每个请求都扔给一个goroutine,当瓶颈出现时会导致大量goroutine堵塞,导致超时,这时候又要对Goroutine池进行流控,又回到之前的问题:池子里设置多少个Goroutine合适?

Actor模型

Actor有如下特征:

Actor遵循如下规则:

Actor的目标:

Actor实现:

Golang CSP vs Actor

两者都是通过消息通信的机制来避免竞态条件,但具体的抽象和实现上有些差异

发送方关心消息类型,以及该写到哪个Channel,不关心谁消费以及多少个消费者,一个Channel只写一种类型的消息,所以CSP需要支持alt/selcet机制,同时监听多个Channel。Channel是同步的模式(Golang的Channel支持buffer,支持一定数量的异步),背后的逻辑是发送方非常关心消息是否被处理,CSP保证每个消息都被正常处理,没被处理就阻塞着

假定发送方关心消息发给谁,不关心类型以及通道。所以是异步模式,不能假定消息一定会被收到和处理。必须支持强大的模式匹配机制,对消息进行分发。背后的逻辑是现实世界本来就应该是异步的,不确定的

CSP的机制比较适合BOSS-Worker模式的任务分发机制,侵入性没有那么强。不试图解决通信的超时容错问题,还是需要发起方进行处理。由于Channel是显式的,当前难透明使用远程Channel。

Actor是一种全新的抽象,面临应用架构机制和思维方式的变更,试图解决容错、分布式。效率无法达到直接调用的效率。折中的方式是将系统的某个层面的组件抽象成Actor。

Rust

Rust解决并发的思路是首先承认世界的资源是有限的,想彻底避免是不可能的。不试图完全避免资源共享,认为并发的问题不在于资源共享,在于错误的使用资源共享。

结论

构想:在Goroutine上实现actor

添加新批注
在作者公开此批注前,只有你和作者可见。
回复批注