文档章节

goroutine, channel 和 CSP

wangxuwei
 wangxuwei
发布于 01/29 21:10
字数 1625
阅读 36
收藏 1

引子

老听 clojure 社区的人提起 core.async ,说它如何好用,如何简化了并发编程的模型,不由得勾起了我的好奇心,想了解一番其思想的源头:CSP 模型及受其启发的 goroutine 和 channel 。

CSP 模型

Tony Hoare - Communicating Sequential Processes

CSP 描述这样一种并发模型:多个Process 使用一个 Channel 进行通信,  这个 Channel 连结的 Process 通常是匿名的,消息传递通常是同步的(有别于 Actor Model)。

CSP 最早是由 Tony Hoare 在 1977 年提出,据说老爷子至今仍在更新这个理论模型,有兴趣的朋友可以自行查阅电子版本:http://www.usingcsp.com/cspbook.pdf

严格来说,CSP 是一门形式语言(类似于 ℷ calculus),用于描述并发系统中的互动模式,也因此成为一众面向并发的编程语言的理论源头,并衍生出了 Occam/Limbo/Golang…

而具体到编程语言,如 Golang,其实只用到了 CSP 的很小一部分,即理论中的 Process/Channel(对应到语言中的 goroutine/channel):这两个并发原语之间没有从属关系, Process 可以订阅任意个 Channel,Channel 也并不关心是哪个 Process 在利用它进行通信;Process 围绕 Channel 进行读写,形成一套有序阻塞和可预测的并发模型。

goroutine

What is a goroutine? It’s an independently executing function, launched by a go statement.
It has its own call stack, which grows and shrinks as required.
It’s very cheap. It’s practical to have thousands, even hundreds of thousands of goroutines.
It’s not a thread.
There might be only one thread in a program with thousands of goroutines.
Instead, goroutines are multiplexed dynamically onto threads as needed to keep all the goroutines running.
But if you think of it as a very cheap thread, you won’t be far off.

― Rob Pike

以上是 Rob Pike 在 Google I/O 2012 上给出的描述,概括下来其实就一句话:

goroutine 可以视为开销很小的线程(既不是物理线程也不是协程,但它拥有自己的调用栈,并且这个栈的大小是可伸缩的  不是协程,它有自己的栈),很好用,需要并发的地方就用 go 起一个 func,goroutine走起 🙂

在 Golang 中,任何代码都是运行在 goroutine里,即便没有显式的 go func(),默认的 main 函数也是一个 goroutine。

但 goroutine 不等于操作系统的线程,它与系统线程的对应关系,牵涉到 Golang 运行时的调度器:

Golang Scheduler

调度器由三方面实体构成:

  1. M:物理线程,类似于 POSIX 的标准线程;
  2. G:goroutine,它拥有自己的栈、指令指针和维护其他调度相关的信息;
  3. P:代表调度上下文,可将其视为一个局部调度器,使Golang代码跑在一个线程上

三者对应关系:

go-scheduler-in-motion

上图有2个 物理线程 M,每一个 M 都拥有一个上下文(P),每一个也都有一个正在运行的goroutine(G)。

P 的数量可由 runtime.GOMAXPROCS() 进行设置,它代表了真正的并发能力,即可有多少个 goroutine 同时运行。

调度器为什么要维护多个上下文P 呢?因为当一个物理线程 M 被阻塞时,P 可以转而投奔另一个OS线程 M(即 P 带着 G 连茎拔起,去另一个 M 节点下运行)。这是 Golang调度器厉害的地方,也是高并发能力的保障。

channel

channel 是 goroutine 之间通信(读写)的通道。因为它的存在,显得 Golang(或者说CSP)与传统的共享内存型的并发模型截然不同,用 Effective Go 里的话来说就是:

Do not communicate by sharing memory; instead, share memory by communicating.

在 Golang 的并发模型中,我们并不关心是哪个 goroutine(匿名性)在用 channel,只关心 channel 的性质:

  • 是只读还是只写?
  • 传递的数据类型?
  • 是否有缓冲区?

比如我希望在程序里并发的计算并传递一个整型值,我就会定义一个 int 型的 channel:

value := make(chan int)

无缓冲的 channel由于 make 这个 channel 并未提供第二个参数capacity,因此这个 channel 是不带缓冲区的,即同步阻塞的channel:

它有如下特点:

1. 不可以在同一个 goroutine 中既读又写,否则将会死锁,抛出如

fatal error: all goroutines are asleep - deadlock!

这样的错误,以下代码片断是这种典型:

func deadlock() {

ch := make(chan int)



ch <- 2

x := <-ch

log.Println(x)

}


2.  两个goroutine中使用无缓冲的channel,则读写互为阻塞,即双方代码的执行都会阻塞在 <-ch 和 ch <- 处,只到双方读写完成在 ch 中的传递,各自继续向下执行,此处借用CSP 图例说明:

CSP commuication semantics

goroutine 在无缓冲 channel 上交互的代码:

func nolock() {

ch := make(chan int)



go func() {

ch <- 2

log.Println("after write")

}()



x := <-ch

log.Println("after read:", x)

}

有缓冲的 channel

在 make 时传递第二参 capacity,即为有缓冲的 channel:

ch := make(chan int, 1)

这样的 channel 无论是否在同一 goroutine 中,均可读写而不致死锁,看看如下片断,你猜它会输出什么:

ch := make(chan int, 1)

for i := 0; i < 10; i++ {

select {

case x := <-ch:

fmt.Println(x)

case ch <- i:

}

}

举个粟子

网上看来的求素数的例子:使用若干个 goroutine (根据求解范围 N 而定)做素数的筛法,即

从2开始每找到一个素数就标记所有能被该素数整除的所有数。直到没有可标记的数,剩下的就都是素数。下面以找出10以内所有素数为例,借用 CSP 方式解决这个问题。

prime filter

代码如下:

package main



import "fmt"



func Processor(seq <-chan int, wait chan struct{}, level int) {

go func() {

prime, ok := <-seq

if !ok {

close(wait)

return

}

fmt.Printf("[%d]: %d\n", level, prime)

out := make(chan int)

Processor(out, wait, level+1)

for num := range seq {

if num%prime != 0 {

out <- num

}

}

close(out)

}()

}



func main() {

origin, wait := make(chan int), make(chan struct{})

Processor(origin, wait, 1)

for num := 2; num < 10; num++ {

origin <- num

}

close(origin)

<-wait

}

FAQ

  • Q:goroutine 什么情况下会产生 leak?
  • A:channel 上只有 send 没有 receive
  • Q:读一个已经关闭的 channel  只会读取到 0 值,有什么办法应对?
  • A:要么在 receive 时加上第二个参数,如 v, ok :=,要么使用 v := range ch 形式接收
  • Q:写一个已经关闭的 channel 会有什么结果?
  • A:会 panic
  • Q:学习 Golang 有什么好的材料
  • A:官网,及下边这本 Golang圣经

the go programming language

 

引用

  1. https://en.wikipedia.org/wiki/Communicating_sequential_processes
  2. https://36kr.com/p/5073181.html
  3. http://arild.github.io/csp-presentation
  4. http://zora.ghost.io/jian-yi-callback-actor-csp/
  5. http://www.jdon.com/concurrent/actor-csp.html
  6. http://www.jtolds.com/writing/2016/03/go-channels-are-bad-and-you-should-feel-bad/
  7. https://blog.golang.org/share-memory-by-communicating
  8. https://talks.golang.org/2012/concurrency.slide
  9. https://www.zhihu.com/question/20862617/answer/27964865
  10. https://blog.golang.org/pipelines

本文转载自:http://www.moye.me/2017/05/05/go-concurrency-patterns/

共有 人打赏支持
wangxuwei
粉丝 24
博文 332
码字总数 115454
作品 0
杭州
其他
golang channel 用法

golang channel 用法转的 一、Golang并发基础理论 Golang在并发设计方面参考了C.A.R Hoare的CSP,即Communicating Sequential Processes并发模型理论。但就像John Graham-Cumming所说的那样,...

wangxuwei
01/29
11
0
Golang Channel用法简编

在进入正式内容前,我这里先顺便转发一则消息,那就是Golang 1.3.2已经正式发布了。国内的golangtc已经镜像了golang.org的安装包下载页面,国内go程序员与爱好者们可以到"Golang中 国",即g...

nop4ss
2015/07/23
177
0
Go基础编程:并发编程—channel

goroutine运行在相同的地址空间,因此访问共享内存必须做好同步。goroutine 奉行通过通信来共享内存,而不是共享内存来通信。 引⽤类型 channel 是 CSP 模式的具体实现,用于多个 goroutine ...

tennysonsky
01/15
0
0
并发之痛 Thread,Goroutine,Actor

并发之痛 Thread,Goroutine,Actor Mar 1, 2016 • jolestar 本文基于我在2月27日Gopher北京聚会演讲整理而成,进行了一些补充以及调整。投稿给《高可用架构》公众号首发。 聊这个话题之前,...

fdhay
2016/03/29
150
1
Concurrency In Golang

Yesterday, I answered a question in Quora about the concurrency model in Go. Now, I feel like I want to say more!! Concurrency in Golang is one of the most powerful features in ......

LsDimplex
2015/12/08
4
0

没有更多内容

加载失败,请刷新页面

加载更多

java序列化(七) - fst 序列化

java序列化(七) - fst 序列化 github https://github.com/RuedigerMoeller/fast-serialization 实践 https://gitee.com/mengzhang6/serializable-demo.git maven依赖 <!-- https://mvnrepo......

晨猫
11分钟前
2
0
智力问题汇总

南京新建地铁线路,给你2块钱,测出来需要配置多少辆车? 参考答案:根据地铁有固定时间间隔,坐一圈该线路,推算出需要多少辆。 一共50张卡片,上面写着1--50 ,50个数字,藏起来一张,打乱...

职业搬砖工程师
15分钟前
1
0
ZFS-自我恢复RAID

ZFS-自我恢复RAID 这个给了我一个简单而又强大的理由,让我立马为之折服,ZFS可以自动的检测发生的错误,而且,可以自我修复这些错误。假设有一个时刻,磁盘阵列中的数据是错误的,不管是什么...

openthings
24分钟前
2
0
从Hash到一致性Hash原理(深度好文)

要讲一致性Hash原理,先从一般性Hash讲起,其实Hash的本质就是一个长度可变的数组,那为什么Hash的时间复杂度是O(1),而其他类型的数据结构查找都是要遍历来,遍历去,即便是树,二叉树,也是要经过几...

算法之名
37分钟前
12
0
软件测试工具书籍与面试题汇总下载(持续更新)

简介 本文是https://github.com/china-testing/python-api-tesing/blob/master/books.md 的节选。 欢迎转载,转载请附带此简介,谢谢! 试题 软件测试综合面试题(高级测试)-试题.pdf 软件测试...

python测试开发人工智能安全
45分钟前
1
0

没有更多内容

加载失败,请刷新页面

加载更多

返回顶部
顶部