目录
一、简介
goroutine 是 Go 里的一种轻量级线程,也叫做协程,Go 语言中,每一个并发的执行单元叫一个 goroutine,是一种轻量级线程。每个 goroutine 都有一个 sched 的属性用来保存它的上下文,在用户态下可以切换,切换的时候不用进入内核态,所以其切换代价非常小。
goroutine 作为 Go 语言的并发利器,不仅性能强劲而且使用方便,且 goroutine 占用内存极小,独立的并发任务比较简单,只需要用go 关键字修饰函数就可以启用一个 goroutine 直接运行,所以开发 go 程序的时候很多开发者常常会使用这个并发工具。
二、数据结构
G、P、M 三者是 Go 实现高并发的最为重要的概念,runtime 通过 调度器 来实现三者的相互调度执行,通过 p 将用户态的 g 与内核态资源 m 的动态绑定来执行,以减少以前通过频繁创建内核态线程而产生的一系列的性能问题,充分发挥服务器最大有限资源。
1、G
G 是 goroutine 的缩写,是运行时的最小执行单元
struct G
{
stackguard uintptr; // 分段栈的可用空间下界
stackbase uintptr; // 分段栈的栈基址
sched gobuf; // 进程切换时,利用 sched 保存上下文
stack stack; // 当前使用的栈空间,包括 [lo, hi]两个成员
stackguard0 uintptr // 用于检测是否需要进行栈扩张,go代码使用
stackguard1 uintptr // 用于检测是否需要进行栈扩展,原生代码使用的
fnstart FuncVal*; // 运行的函数
param void*; // 传递参数
status int16; // 状态
goid int64; // id 号
m m*; // 当前 g 所绑定的m
lockedm muintptr // g 是否要求要回到这个 M 执行
gopc uintptr; // 创建这个 goroutine 的go表达式的 pc
...
};
2、P
P 指 Process, 是 M 运行 G 所需的资源,协程调度器的基本调度单元,G的容身之所,连接G和M的桥梁。
// process 结构
type p struct {
lock mutex // 互斥锁
id int32 // id
status uint32 // p的状态,稍后介绍
link puintptr // 下一个p的地址,可参考 g.schedlink
m muintptr // p所关联的m
mcache *mcache // 内存分配的时候用的,p所属的m的mcache用的也是这个
goidcache uint64 // 从sched中获取并缓存的id,避免每次分配goid都从sched分配
goidcacheend uint64
runqhead uint32 // p 本地的runnbale的goroutine形成的队列
runqtail uint32
runq [256]guintptr
runnext guintptr // 下一个执行的g,如果是nil,则从队列中获取下一个执行的g
gfree *g // 状态为 Gdead的g的列表,可以进行复用
gfreecnt int32
}
3、M
M 指 machine,代表操作系统线程,也就是我们经常理解的线程,是真正参与操作系统调度的单元,每个 goroutine 只有依附于某个 M 方可真正地执行。
// machine 数据结构
type m struct {
g0 *g // g0 是用于调度和执行系统调用的特殊 g
curg *g // m 当前运行的 g
p puintptr // 当前拥有的 p
tls [6]uintptr // 线程的 local storage
nextp puintptr // 唤醒m时,m会拥有这个p
id int64 // id
preemptoff string // 如果 !="", 继续运行curg
spinning bool // 自旋状态,用于判断 m 是否工作已结束,并寻找 g 进行工作
blocked bool // 用于判断m是否进行休眠状态
park note // 通过这个休眠和唤醒 m
alllink *m // 所有 m 组成的一个链表
schedlink muintptr // 下一个m,通过这个字段和 sched.midle 可以串成一个 m 的空闲链表
mcache *mcache // mcache,m 拥有 p 的时候,会把自己的 mcache 给 p
lockedg guintptr // lockedm 的对应值
freelink *m // 待释放的 m 的list,通过 sched.freem 串成一个链表
}
G P M 三者之间的关系为:
三、菜鸟实战
实战需求:运行一个含有 goroutine 的程序
马上安排!
1、创建 g008.go
/*
* @Author: 菜鸟实战
* @FilePath: /go110/go-008/g008.go
* @Description: goroutine
*/
package main
import (
"fmt"
"runtime"
"time"
)
// 测试函数
func test_goroutine(from string) {
for i := 0; i < 3; i++ {
// 打印当前序号
fmt.Println(from, ":", i)
}
}
func main() {
// 使用内置函数打印
println("Hello", "菜鸟实战")
// 开启一个 goroutine
go test_goroutine("G")
// 开启一个 goroutine
go test_goroutine("M")
// 开启一个 goroutine
go func(msg string) {
fmt.Println(msg)
}("P")
// 等待
time.Sleep(time.Second)
fmt.Println("done")
// 当前版本
fmt.Printf("版本: %s \n", runtime.Version())
}
2、编译和运行
# 1、生成模块依赖
go mod init g008
# 2、编译
go build g008.go
# 3、编译后的目录结构
└── go-008
├── g008
├── g008.go
└── go.mod
# 4、运行
go run g008
3、运行结果
Hello 菜鸟实战
G : 0
G : 1
P
G : 2
M : 0
M : 1
M : 2
done
版本: go1.17.10
Hello 菜鸟实战
G : 0
G : 1
G : 2
P
M : 0
M : 1
M : 2
done
版本: go1.17.10
Hello 菜鸟实战
P
G : 0
G : 1
G : 2
M : 0
M : 1
M : 2
done
版本: go1.17.10
菜鸟实战,持续学习!