前言
在开始学习 Go 特性的并发编程之前,先来简单了解一些基础概念,这对理解学习 Go 并发模型会有一些帮助。
概念
内核态
也被称为内核空间,是系统内核的运行空间,与用户空间隔离。控制计算机的硬件资源。
用户态
也被称为用户空间,代码运行在较低的特权级别上,不能直接访问内核空间和硬件设备。
系统调用
用户空间不能直接调用系统资源,必须通过调用系统接口,由内核完成相关指令。
进程
进程是操作系统分配资源的最小单元。
线程
有时也被称为轻量级进程(LWP),是被系统独立调度和分派的基本单位,线程自己不拥有系统资源,只拥有一点儿在运行中必不可少的资源,但它可与同属一个进程的其它线程共享进程所拥有的全部资源。
KSE
理论上说 Linux 内核是没有线程这个概念的,只有内核调度实体(Kernal Scheduling Entry)这个概念。
协程
有时也被称为微线程(Coroutine),只不过这个线程是用户态的,不需要操作系统参与。
调度过程示例:
- coroutine A 进行 网络I/O,使用 write() 发送数据后调用 read() 等待数据的返回,但是不一定什么时候能够读到数据,所以将 Socket Fd 放入 EventLoop 中。
- coroutine A 让出 CPU 给 其它 coroutine 使用 —— 挂起(yield)
- 待 read() 有返回数据后,继续执行 coroutine A —— 恢复(resume)
EventLoop:无限循环着的事件分发器,一旦有事件发生,它就会回调预先定义好的回调函数,完成事件的处理。
线程模型
用户级线程
M:1 多个用户空间的线程,对应一个内核空间的 KSE。
系统级线程
1:1 1个用户空间的线程,对应1个内核空间的 KSE。
两级线程模型(混合式线程模型)
M:N 多个用户空间的线程,对应多个内核空间的 KSE。
并发模型
并发模型指定了系统中的线程如何通过协作来完成分配给它们的作业。不同的并发模型采用不同的方式拆分作业,同时线程间的协作和交互方式也不相同。
常见的有:
- 线程&锁
- Actor
- CSP
- Lambda 架构
并发模型—CSP
Communicating Sequential Processes 通信顺序进程。
看这个名字就能知道,在CSP模型中主角是通信。
channel:进行通信的并发模型。
worker:channel 两端的发送者/接收者。
worker 之间不直接彼此联系,而是通过 channel 进行消息发送和接收。
在并发模型中,经常和 CSP 一起讨论的是 Actor 模型。
GO 线程模型
Go 参考了两级线程模型,设计出了 MPG 线程模型。
M
machine
用户空间内的线程,与 KSE 一一对应,可以理解为 M 代表了 内核线程。
P
processor
可以理解为用户代码逻辑的处理器,Go 代码片段所必需的上下文环境。
G
goroutine
Go 代码片段的封装。
go function (){
// ...
}()
GO并发模型
channel
参考了 CSP并发模型。
扩展
goroutine 与 coroutine 的区别
coroutine
运行机制属于协作式任务处理,始终顺序执行。
goroutine
运行机制属于抢占式任务处理,可能发生并行执行。
总结
- Go 的并发模型参考了 CSP并发模型,采用 channel 进行通信。
- Go 的线程模型参考了两级线程模型,设计出了 MPG 模型。
- goroutine 与 coroutine 的区别就是前者可能发生并行执行,后者始终顺序执行。
Sown专栏地址:https://segmentfault.com/blog/sown
有疑问加站长微信联系(非本文作者)