并行、并发
并行和并发的区别:
- 并行:两个或多个程序在同一时刻执行。
- 并发:两个或多个程序在同一个时间段内执行。
并行执行的程序,在同一时刻,是真真正正的有多个程序在 CPU 上执行,这也就需要 CPU 提供多核计算的能力。而并发执行的程序,只是在宏观的角度观察到有多个程序在 CPU 上执行,微观上是它们在 CPU 上被快速轮换执行。
对于进程、线程、协程,并发、并行,在我之前的文章中讲并发掌握时也有介绍过,感兴趣的可以过去瞅一眼。传送门在此Go通关09:并发掌握,goroutine和channel声明与使用!
Go 的 MPG 线程模型
之所以 Go 被认为是高性能开发语言,在于它在原生态支持协程并发。协程是一种用户线程,是轻量级线程。协程的调度完全取决于用户空间的代码控制。
协程拥有自己的寄存器上下文和栈,并存储在用户空间,协程在切换时无需切换到内核态来访问内核空间,切换速度极快。开发人员需要在用户空间处理协程切换时候的上下文信息的保存和恢复、栈空间大小的管理等技术问题。
Go语言采用了一种特殊的两级线程模型,即 MPG 线程模型:
- M,即 machine,相当于内核线程在 Go 进程中的映射,它与内核线程一一对应,代表真正执行计算的资源。M 的生命周期内,只会与一个内核线程相关联。
- P,即 processor,代表 Go 代码片段执行所需的上下文环境。 M 和 P 的结合可以为 G 提供有效的运行环境。它们之间的结合关系不是固定的。P 的最大数量决定了 Go 程序的并发规模,由 runtime.GOMAXPROCS 变量来决定。
- G,即 goroutine,是一种轻量级的用户线程,是对代码片段的封装,拥有执行时的栈、状态和代码片段等信息。
在实际执行过程中,多个可执行的 G 会顺序挂载在 P 的可执行 G 队列下面,等待调度和指向。当 G 中存在一些 I/O 系统调用阻塞了 M 时,P 会断开 M 的联系,从调度器空闲 M 队列中获取一个 M 或创建一个新的 M 进行组合执行,从而保证 P 中可执行 G 队列中其他 G 得到执行,由于程序中并行执行的 M 数量没有变,所以程序的 CPU 有很高的利用率。
1. select 多路复用
select 可以实现多路复用,即同时监听多个 channel。
- 发现哪个 channel 有数据产生,就执行相应的 case 分支
- 如果同时有多个 case 分支可以执行,则会随机选择一个
- 如果一个 case 分支都不可执行,则 select 会一直等待
示例:
package mainimport (
"fmt"
)func main() {
ch := make(chan int, 1)
for i := 0; i < 10; i++ {
select {
case x :=