0. 简介

goroutinechannelgoroutinechannel

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

CSP(Communicating Sequential Processes)
goroutine

1. 进程、线程和协程

进程,是一段程序的执行过程,是指令、数据及其组织形式的描述,进程是正在执行的程序的实例。进程拥有自己的独立空间。

传统的操作系统中,每个进程有一个地址空间和至少一个控制线程,这几乎可以认为是进程的定义。而这个地址空间中,可以存在多个控制线程的情形,这些线程可以理解为轻量级的进程,除了他们共享地址空间。多线程有以下好处:

  • 在许多应用中同时发生着多种活动,其中某些活动会被阻塞,比如I/O操作,而某些程序则需要响应迅速,比如界面请求,因此多线程的程序设计模型会变得更简单;
  • 线程比进程更加轻量级,所以其创建、销毁和上下文切换都更快;
  • 在多CPU的系统中,多线程可以实现真正的并行。

在操作系统中,进程是操作系统资源分配的单位;线程是处理器调度和执行的基本单位。

Linux中的进程和线程

在Linux中,所有的线程都当做进程来实现,二者的区别在于:进程拥有自己的页表(即地址空间),而线程没有,只能和同一进程内的其他线程共享同一份页表。这个区别的根本原因在于二者调用系统时的传参不同而已。

fork()flagsSIGCHLDpthread_createcloneflags
clone
clone

1.1 线程模型

线程可以分为内核线程和用户线程,用户线程必须依托于内核线程,实现调度,这样就带来了三种线程模型:多对一(M:1)、一对一(1:1)和多对多(M:N)(用户线程对内核线程)。一个用户线程必须绑定一个内核线程才能执行,不过CPU并不知道有用户线程的存在。

1.1.1 多对一用户级线程模型

这种模型是多个用户线程对应一个内核调度线程,所有的线程的创建、销毁和调度都由用户空间的线程库实现,内核不感知这些线程的切换。优点是线程的上下文切换之间不需要陷入内核,速度快。缺点是一旦有一个用户线程有阻塞性的系统调用,比如I/O操作时,系统内核接管后,会阻塞所有的线程。另外,在多处理器的机器上,这种线程模型是没有意义的,无法发挥多核系统的优势。

1.1.2 一对一内核级线程模型

NPTL(Native POSIX Threads Library)

在程序中,我们创建了两个线程,执行如下:

$ gcc thread.c -o thread_c -lpthread

$ ./thread_c
创建线程1
arg is NULL
创建线程2
I am p2!

然后查看进程号和此进程下的线程数。

$ ps -ef | grep thread_c
chenyig+   5293   5087  0 19:02 pts/0    00:00:00 ./thread_c
chenyig+   5459   5347  0 19:03 pts/1    00:00:00 grep --color=auto thread_c

$ cat /proc/5293/status | grep Threads
Threads:    3

1:1

1.1.3 多对多两级线程模型

1:1M:1NPTL1:1Gogoroutine

运行后:

$ go build -o thread_go goroutine.go

$ ./thread_go
I am goroutine 7
I am goroutine 4
I am goroutine 0
I am goroutine 6
I am goroutine 1
I am goroutine 2
I am goroutine 9
I am goroutine 3
I am goroutine 5
I am goroutine 8

然后查看进程号和此进程下的线程数。

$ ps -ef | grep thread_go
chenyig+  69705  67603  0 17:17 pts/0    00:00:00 ./thread_go
chenyig+  69735  68420  0 17:17 pts/2    00:00:00 grep --color=auto thread_go

$ cat /proc/69705/status | grep Threads
Threads:    5

可以看到,用户线程(goroutine)和内核线程并不是一一对应的,而是多对多的情形。

2. GMP模型

Go在2012年正式引入GMP模型,然后在1.2版本中引入了协作式的抢占式调度,在1.14版本中实现了基于信号的抢占式调度,并一直沿用至今。

GMP模型中:

GoroutineMachineProcessor

2.1 G

Goroutineruntime
Goroutineruntime.g
Gostackstackguard0ggo栈stackguard1g0gsignal

另外,还有以下三个字段和抢占息息相关。

m
schedGoroutine

其中:

sppcctxtDXbp
goroutine

2.2 M

MGoruntime.mruntime.g
g0goroutinecurggoroutineg0goroutineCGO
runtime.mPpnextpoldptlstlsm

2.3 P

PMGPgoroutinegoroutine
GOMAXPROCSGoGOMAXPROCS
runtime.pPmrunqgoroutinerunnextgoroutine
GMPGMPGo

3. 基础调度过程

GMPPGMgoroutine~0.2us~1usgoroutine2KB1Mgoroutine
GMgoroutineGgoroutinePruntime.GOMAXPROCSMMGPMMMMMGM