02.13 GO 语言之 Goroutine 原理解析

并发 一个 CPU 上能同时执行多项任务,在很短时间内,CPU 来回切换任务执行(在某段很短时间内执行 程序 a,然后又迅速得切换到程序 b 去执行),有时间上的重叠(宏观上是同时的,微观仍是顺序执行),这样看起来多个任务像是同时执行,这就是并发。

并行 当系统有多个 CPU 时,每个 CPU 同一时刻都运行任务,互不抢占自己所在的 CPU 资源,同时进行, 称为并行。

进程 CPU 在切换程序的时候,如果不保存上一个程序的状态(context --上下文),直接切换下一个程 序,就会丢失上一个程序的一系列状态,于是引入了进程这个概念,用以划分好程序运行时所需 要的资源。因此进程就是一个程序运行时候的所需要的基本资源单位(也可以说是程序运行的一 个实体)。

线程 CPU 切换多个进程的时候,会花费不少的时间,因为切换进程需要切换到内核态,而每次调度需 要内核态都需要读取用户态的数据,进程一旦多起来,CPU 调度会消耗一大堆资源,因此引入了 线程的概念,线程本身几乎不占有资源,他们共享进程里的资源,内核调度起来不会那么像进程 切换那么耗费资源。协程 协程拥有自己的寄存器上下文和栈。协程调度切换时,将寄存器上下文和栈保存到其他地方,在 切回来的时候,恢复先前保存的寄存器上下文和栈。因此,协程能保留上一次调用时的状态(即 所有局部状态的一个特定组合),每次过程重入时,就相当于进入上一次调用的状态,换种说 法:进入上一次离开时所处逻辑流的位置。线程和进程的操作是由程序触发系统接口,最后的执 行者是系统;协程的操作执行者则是用户自身程序,goroutine 也是协程。


Go 并发模式

生产者消费者模型

  • 该模式主要通过平衡生产线程和消费线程的工作能力来提高程序的整体处理数据的速度。
  • 生产者生产一些数据,然后放到成果队列中,同时消费者从成果队列中来取这些数据。这样 就让生产消费变成了异步的两个过程。
  • 当成果队列中没有数据时,消费者就进入饥饿的等待中;而当成果队列中数据已满时,生产 者则面临因产品挤压导致 CPU 被剥夺的下岗问题。
<code>packagemain
import(
"fmt"
"time"
)
// 生产者: 生成 factor 整数倍的序列
funcProducer(factorint, outchanfori :=0; ; i++ {
out }
}
// 消费者
funcConsumer(in forv :=rangein {
fmt.Println(v)
}
}

funcmain(){
ch :=make(chanint,64)// 成果队列
goProducer(3, ch)// 生成 3 的倍数的序列
goProducer(5, ch)// 生成 5 的倍数的序列
goConsumer(ch)// 消费 生成的队列
// 运行一定时间后退出
time.Sleep(5* time.Second)
}/<code>


Go 并发模型

Go 语言的并发处理参考了 CSP(Communicating Sequential Process 通讯顺序进程)模型。CSP 并发模型是 Hoare 在 1978 年提出的 CSP 的概念,不同于传统的多线程通过共享内存来通信, CSP 有着精确的数学模型,并实际应用在了 Hoare 参与设计的 T9000 通用计算机上。CSP 讲究的是“以通信的方式来共享内存”。

Don’t communicate by sharing memory; instead, share memory by communicating. 不要通过共享内存来通信,而应通过通信来共享内存。


Go 的 CSP 模型实现与原始的 CSP 实现有点差别:原始的 CSP 中 channel 里的任务都是立即执行的, 而 go 语言为其增加了一个缓存,即任务可以先暂存起来,等待执行线程准备好再顺序执行。

Go 的 CSP 并发模型,是通过 goroutine 和 channel 来实现的。

  • goroutine 是 Go 语言中并发的执行单位。有点抽象,其实就是和传统概念上的”线程“类似, 可以理解为”线程“。
  • channel 是 Go 语言中各个并发结构体(goroutine)之前的通信机制。通俗的讲,就是各个 goroutine 之间通信的”管道“,有点类似于Linux 中的管道。

生成一个 goroutine 的方式非常的简单:Go 一下,就生成了。

<code>gof()/<code>

通信机制 channel 也很方便,传数据用 channel


Go 调度器 GMP

Go 语言运行时环境提供了非常强大的管理 goroutine 和系统内核线程的调度器, 内部提供了三种 对象:Goroutine,Machine,Processor。

Goroutine : 指应用创建的 goroutine

Machine : 指系统内核线程。

Processor : 指承载多个 goroutine 的运行器

在宏观上说,Goroutine 与 Machine 因为 Processor 的存在,形成了多对多(M:N)的关系。M个 用户线程对应N个系统线程,缺点增加了调度器的实现难度.

Goroutine 是 Go 语言中并发的执行单位。Goroutine 底层是使用协程 (coroutine) 实现,coroutine 是一种运行在用户态的用户线程(参考操作系统原理:内核态,用户态)它可以由语言和框架层 调度。Go 在语言层面实现了调度器,同时对网络,IO 库进行了封装处理,屏蔽了操作系统层面的 复杂的细节,在语言层面提供统一的关键字支持。

三者与内核级线程的关系如下图所示:

GO 语言之 Goroutine 原理解析

一个 Machine 会对应一个内核线程(K),同时会有一个 Processor 与它绑定。一个 Processor 连 接一个或者多个Goroutine。Processor有一个运行时的Goroutine(上图中绿色的G),其它的 Goroutine 处于等待状态。

Processor 的数量同时可以并发任务的数量,可通过 GOMAXPROCS 限制同时执行用户级任务的操 作系统线程。GOMAXPROCS 值默认是 CPU 的可用核心数,但是其数量是可以指定的。在 go 语言 运行时环境,可以使用

<code>runtime.GOMAXPROCS(MaxProcs)/<code>

来指定 Processor 数量。

  • 当一个 Goroutine 创建被创建时,Goroutine 对象被压入 Processor 的本地队列或者 Go 运行时 全局 Goroutine 队列。
  • Processor 唤醒一个 Machine,如果 Machine 的 waiting 队列没有等待被 唤醒的 Machine, 则创建一个(只要不超过 Machine 的最大值,10000),Processor 获取到 Machine 后,与 此 Machine 绑定,并执行此 Goroutine
<code>funcschedinit(){
//设置最大的M数量
sched.maxmcount =10000
}/<code>
  • Machine 执行过程中,随时会发生上下文切换。当发生上下文切换时,需要对执行现场进行 保护,以便下次被调度执行时进行现场恢复。Go 调度器中 Machine 的栈保存在 Goroutine 对 象上,只需要将 Machine 所需要的寄存器(堆栈指针、程序计数器等)保存到 Goroutine 对象上 即可。
  • 如果此时 Goroutine 任务还没有执行完,Machine 可以将 Goroutine 重新压入 Processor 的队 列,等待下一次被调度执行。
  • 如果执行过程遇到阻塞并阻塞超时,Machine 会与 Processor 分离,并等待阻塞结束。此时 Processor 可以继续唤醒 Machine 执行其它的 Goroutine,当阻塞结束时,Machine 会尝试” 偷取”一个 Processor,如果失败,这个 Goroutine 会被加入到全局队列中,然后 Machine 将 自己转入 Waiting 队列,等待被再次唤醒。

51Reboot 2.13号分享《Goroutine 原理解析》

内容:

1、goroutine 原理解析

  • GO 并发模式
  • GO 并发模型
  • GO 调度器 GMP

2、channel 原理 channel 数据结构

  • channel 实现方式

地址:https://ke.qq.com/course/860855?taid=5461862666543799&tuin=31589b0e


分享到:


相關文章: