Golang 并发的一些我自己才看得懂的总结
- Goroutine 是绿色线程,下面自带调度器。可以在 syscall 进入阻塞状态的时候自动出让 CPU(类似 Java 在进入锁以前自动引入自旋,这实际上是一种抢占式调度—preemptive scheduling),也可以通过
runtime.Gosched()
主动出让 CPU,调度器还可能无缘由地主动抢占 Goroutine 的时间片(比如已经运行了10ms)。因为是绿色线程,所以可以很便宜地创造百万Goroutine。在 Go 1.5 以后,可以通过 GOMAXPROCS 来使用更多的逻辑 CPU(而不也是系统进程)来利用多核。主线程不是主线程,主线程也是一个 main goroutine。 - Go 关键字基本就等同于 Java 中提交一个 Runnable 到 CompletableFuture 的 CommonPool。在没有 Channel 的帮助时,goroutine 几乎可以等同于一个绿色的守护线程。
- Go 也是有 mutex 的,但是不提倡使用,用 channel 最好。 share memory by communicating。
- channel 是通过描述若干操作的模式匹配来实现 select 的。它的无 buffer 版本和有 buffer版本类似于 SynchronousQueue 和 BlockingQueue。这个东西是化异步为同步的利器。default 和 timeout 可以让阻塞操作变为非阻塞的操作。channel 的阻塞也是会引起 Goroutine 的调度的。
- channel 是 CSP(Communicating Sequential Proceesses)模型的实现。
- 单向 channel 有点像泛型里的单边操作的 wildchar,可以封印掉读/写能力,而且需要通过声明强转。
- channel 可以被关闭。但关闭有什么用呢?不关闭它也会被垃圾回收,关闭只是 sender 给 reciever 发送的一个状态变化。
- 和其他方案的对比
- 异步回调会把程序拆得七零八落。人脑还是线性思维的。化异步为同步是最重要的。 Goroutine 就是化异步为同步的。
- GreenThread/Coroutine/Fiber方案 遇到阻塞的时候,可以自己 yield 出 CPU,但还是需要外部的线程把 context resume 回来。Go 则由 scheduler 自动识别代劳了。需要外部线程来不断切换 context,其实是一种单线程的并发,尽量减少阻塞时间而能够多利用 CPU 罢了。
- 因为有了遇到IO阻塞的时候自动 yield 的机制,所以其他机制中 M:1 或者 M:N 的时候,单线程可能会用 IO 系统调用阻塞整个进程的情况,不再发生。
- 调度器的细节:
调度器就好像是一个分层调度的线程池。M 代表 物理处理器(其实是 machine 的意思),P 代表逻辑处理器,G 代表 Goroutine。Go 实现了 M:N 的调度(而不是1:M 的调度),G 通过 P(只看到P),可以在不同的 M 之间自由切换,这是其他 Green Thread 做不到的(因为其他 Green Thread 本质上还是但进程/系统线程)。G 可以阻塞,M永远不阻塞。这方面的出处见这里。注意看图,这里面还是用的工作窃取算法。调度器的细节可以查看《也谈goroutine调度器》。 - 更多的例子见Go by Example,《并发之痛 Thread,Goroutine,Actor》、《Go 调度器: M, P 和 G》和《golang大杀器GMP模型》。
All articles on this blog are licensed under CC BY-NC-SA 4.0 unless otherwise stated.