本文小编为大家详细介绍“Golang并发编程之GMP模型怎么实现”,内容详细,步骤清晰,细节处理妥当,希望这篇“Golang并发编程之GMP模型怎么实现”文章能帮助大家解决疑惑,下面跟着小编的思路慢慢深入,一起来学习新知识吧。
0. 简介
goroutinechannelgoroutinechannelDo not communicate by sharing memory; instead, share memory by communicating.
CSP(Communicating Sequential Processes)1. 进程、线程和协程
进程,是一段程序的执行过程,是指令、数据及其组织形式的描述,进程是正在执行的程序的实例。进程拥有自己的独立空间。
传统的操作系统中,每个进程有一个地址空间和至少一个控制线程,这几乎可以认为是进程的定义。而这个地址空间中,可以存在多个控制线程的情形,这些线程可以理解为轻量级的进程,除了他们共享地址空间。多线程有以下好处:
在许多应用中同时发生着多种活动,其中某些活动会被阻塞,比如I/O操作,而某些程序则需要响应迅速,比如界面请求,因此多线程的程序设计模型会变得更简单;
线程比进程更加轻量级,所以其创建、销毁和上下文切换都更快;
在多CPU的系统中,多线程可以实现真正的并行。
在操作系统中,进程是操作系统资源分配的单位;线程是处理器调度和执行的基本单位。
Linux中的进程和线程
在Linux中,所有的线程都当做进程来实现,二者的区别在于:进程拥有自己的页表(即地址空间),而线程没有,只能和同一进程内的其他线程共享同一份页表。这个区别的根本原因在于二者调用系统时的传参不同而已。
fork()flagsSIGCHLDpthread_createcloneflagsconst int clone_flags = (CLONE_VM | CLONE_FS | CLONE_FILES | CLONE_SYSVSEM | CLONE_SIGHAND | CLONE_THREAD | CLONE_SETTLS | CLONE_PARENT_SETTID | CLONE_CHILD_CLEARTID | 0);
cloneint clone(int (* fn )(void *), void * stack , int flags , void * arg , ... /* pid_t * parent_tid , void * tls , pid_t * child_tid */ );
clone1.1 线程模型
线程可以分为内核线程和用户线程,用户线程必须依托于内核线程,实现调度,这样就带来了三种线程模型:多对一(M:1)、一对一(1:1)和多对多(M:N)(用户线程对内核线程)。一个用户线程必须绑定一个内核线程才能执行,不过CPU并不知道有用户线程的存在。
1.1.1 多对一用户级线程模型
这种模型是多个用户线程对应一个内核调度线程,所有的线程的创建、销毁和调度都由用户空间的线程库实现,内核不感知这些线程的切换。优点是线程的上下文切换之间不需要陷入内核,速度快。缺点是一旦有一个用户线程有阻塞性的系统调用,比如I/O操作时,系统内核接管后,会阻塞所有的线程。另外,在多处理器的机器上,这种线程模型是没有意义的,无法发挥多核系统的优势。
1.1.2 一对一内核级线程模型
NPTL(Native POSIX Threads Library)#include <stdio.h>
#include <unistd.h>
#include <pthread.h>
void *f(void *arg){
if (!arg) {
printf("arg is NULL\n");
} else {
printf("%s\n", (char *)arg);
}
sleep(100);
return NULL;
}
int main() {
pthread_t p1, p2;
int res;
char *p2String = "I am p2!";
// 创建p1线程
res = pthread_create(&p1, NULL, f, NULL);
if (res != 0) {
printf("创建线程1失败!\n");
return 0;
}
printf("创建线程1\n");
sleep(5);
// 创建p1线程
res = pthread_create(&p2, NULL, f, (void *)p2String);
if (res != 0) {
printf("创建线程2失败!\n");
return 0;
}
printf("创建线程2\n");
sleep(100);
return 0;
}在程序中,我们创建了两个线程,执行如下:
$ 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:11.1.3 多对多两级线程模型
1:1M:1NPTL1:1Gogoroutinepackage main
import (
"fmt"
"sync"
"time"
)
func f(i int) {
fmt.Printf("I am goroutine %d\n", i)
time.Sleep(100 * time.Second)
}
func main() {
wg := sync.WaitGroup{}
for i := 0; i < 100; i++ {
idx := i
wg.Add(1)
go func() {
defer wg.Done()
f(idx)
}()
}
wg.Wait()
}运行后:
$ 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模型中:
GoroutineMachineProcessor2.1 G
GoroutineruntimeGoroutineruntime.gtype g struct {
// Stack parameters.
// stack describes the actual stack memory: [stack.lo, stack.hi).
// stackguard0 is the stack pointer compared in the Go stack growth prologue.
// It is stack.lo+StackGuard normally, but can be StackPreempt to trigger a preemption.
// stackguard1 is the stack pointer compared in the C stack growth prologue.
// It is stack.lo+StackGuard on g0 and gsignal stacks.
// It is ~0 on other goroutine stacks, to trigger a call to morestackc (and crash).
stack stack // offset known to runtime/cgo
stackguard0 uintptr // offset known to liblink
stackguard1 uintptr // offset known to liblink
...
}Gostackstackguard0ggo栈stackguard1g0gsignal// Stack describes a Go execution stack.
// The bounds of the stack are exactly [lo, hi),
// with no implicit data structures on either side.
type stack struct {
lo uintptr
hi uintptr
}另外,还有以下三个字段和抢占息息相关。
type g struct {
...
preempt bool // preemption signal, duplicates stackguard0 = stackpreempt
preemptStop bool // transition to _Gpreempted on preemption; otherwise, just deschedule
preemptShrink bool // shrink stack at synchronous safe point
...
}mtype g struct {
...
m *m // current m; offset known to arm liblink
sched gobuf
...
}schedGoroutinetype gobuf struct {
// The offsets of sp, pc, and g are known to (hard-coded in) libmach.
//
// ctxt is unusual with respect to GC: it may be a
// heap-allocated funcval, so GC needs to track it, but it
// needs to be set and cleared from assembly, where it's
// difficult to have write barriers. However, ctxt is really a
// saved, live register, and we only ever exchange it between
// the real register and the gobuf. Hence, we treat it as a
// root during stack scanning, which means assembly that saves
// and restores it doesn't need write barriers. It's still
// typed as a pointer so that any other writes from Go get
// write barriers.
sp uintptr
pc uintptr
g guintptr
ctxt unsafe.Pointer
ret uintptr
lr uintptr
bp uintptr // for framepointer-enabled architectures
}其中:
sppcctxtDXbpgoroutine2.2 M
MGoruntime.mruntime.gtype m struct {
g0 *g // goroutine with scheduling stack
...
curg *g // current running goroutine
...
}g0goroutinecurggoroutineg0goroutineCGOruntime.mPpnextpoldptlstlsmtype m struct {
...
p puintptr // attached p for executing go code (nil if not executing go code)
nextp puintptr
oldp puintptr // the p that was attached before executing a syscall
...
tls [tlsSlots]uintptr // thread-local storage (for x86 extern register)
...
}2.3 P
PMGPgoroutinegoroutineGOMAXPROCSGoGOMAXPROCStype p struct {
...
m muintptr // back-link to associated m (nil if idle)
...
// Queue of runnable goroutines. Accessed without lock.
runqhead uint32
runqtail uint32
runq [256]guintptr
runnext guintptr
...
}runtime.pPmrunqgoroutinerunnextgoroutineGMPGMPGo3. 基础调度过程
GMPPGMgoroutine~0.2us~1usgoroutine2KB1MgoroutineGMgoroutineGgoroutinePruntime.GOMAXPROCSMMGPMMMMMGM