这是我参与「第五届青训营 」笔记创作活动的第13天
Goroutines&Channels
CSP模型
提到Go语言必然会提到其并发编程,其中最大的两个亮点一个是goroutine,一个是chan。二者合体即支持顺序通信进程,即CSP。
CSP是一种现代的并发编程模型,是上个世纪七十年代提出的,用于描述两个独立的并发实体通过共享的通讯 channel(管道)进行通信的并发模型。
更为传统的并发模型是多线程共享内存,但在学习Go的过程中,我们也许会听到这样一句话:
不要通过共享内存来通信,而要通过通信来实现内存共享
这就是 Go 的并发哲学,它依赖 CSP 模型,基于Goroutines&Channels实现。
Goroutines
goroutine是一个函数或者方法,来和其他的函数或者方法并行的运行。gorotine可以看成一个占用资源很小的轻量型线程。
上学期在学校里的操作系统课刚学到
进程是操作系统分配资源的基本单位,而线程是操作系统调度的基本单位。
当然线程还要分操作系统可感知的内核级线程和不可感知的用户级线程。那gorotine到底属于哪种,又是怎么调度的呢?
既然要轻量,自然是要更少的切换开销,抛开需要核心态用户态切换的开销极大的纯粹内核级线程,全部运行在一个进程上的纯粹的用户级线程开销最少,但也有以下问题:
- 系统调用引发阻塞,一个线程执行一个系统调用时,不仅这个线程会被阻塞,进程中的所有线程都会被阻塞。
- 应用程序无法利用多处理器技术
协程采用了组合的方法,完全运行在用户态中,借鉴了N:M模型,我们称之为GMP模型
- G:gorotine——协程
- M:machine——内核线程
- P:processor——本地调度器
P维护一个本地队列,当创建了一个G时会添加到该P的本地队列中,当M被系统唤醒时,会与P进行绑定,并获取一个G执行。还实现了以下的机制
- 本地队列P满了,会把本地队列的一半转移到全局队列中;本地队列为空时,从全局队列获取
- 当本线程没有可运行的协程G时,尝试从其他内核线程M绑定的本地队列P,而不是销毁线程
- 当本线程因为G进行系统调用而阻塞的时候,内核线程会释放本地队列,将P转移给其他空闲线程执行
基于GMP模型,gorotine与操作系统的线程有着很大区别,基于自身而非操作系统有着以下特性:
- 动态栈
- 无ID号
- 调度器不基于硬件计时器
Channels
channel是goroutine之间通信的通道,类似消息队列,符合FIFO,是内存级别的通信。
- 无缓冲chan:读方和写方无论谁先进行到此处,都会阻塞以等待另一方执行,即做一次同步操作
- 有缓存chan:有缓冲的通道并不强制channel的读写者必须同时完成发送和接收,读者只会在没有数据时阻塞,写者只会在没有可用容量时阻塞。
无缓冲chan与缓冲为1的chan区别:无缓冲写方和读方都在操作前阻塞,而缓冲为1的chan写方可以正常写入一个数据,若读方没有读取,则第二次再运行到写入的时候才被阻塞。
可以由多个chan串联,设置适当的缓冲区,避免因为各部分的处理速度不同造成阻塞
可以使用单方向chan,防止被滥用
基于select关键字可以实现多路复用chan