【Go实战基础】轻量级线程 goroutine
目录
一、简介
二、数据结构
1、G
2、P
3、M
1、创建 g008.go
2、编译和运行
3、运行结果
一、简介
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*; // 传递参数 statusint16; // 状态 goid int64; // id 号 m m*; // 当前 g 所绑定的mlockedm 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 进行工作 blockedbool // 用于判断m是否进行休眠状态 park note // 通过这个休眠和唤醒 m alllink*m // 所有 m 组成的一个链表 schedlink muintptr // 下一个m,通过这个字段和 sched.midle 可以串成一个 m 的空闲链表 mcache *mcache // mcache,m 拥有 p 的时候,会把自己的 mcache 给 p lockedgguintptr // lockedm 的对应值 freelink *m // 待释放的 m 的list,通过 sched.freem 串成一个链表}
G P M 三者之间的关系为:
三、菜鸟实战
实战需求:运行一个含有 goroutine 的程序
马上安排!
1、创建 g008.go
/* * @Author: 菜鸟实战 * @FilePath: /go110/go-008/g008.go * @Description: goroutine */package mainimport ("fmt""runtime""time")// 测试函数func test_goroutine(from string) {for i := 0; i < 3; i++ {// 打印当前序号fmt.Println(from, ":", i)}}func main() {// 使用内置函数打印println("Hello", "菜鸟实战")// 开启一个 goroutinego test_goroutine("G")// 开启一个 goroutinego test_goroutine("M")// 开启一个 goroutinego 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
菜鸟实战,持续学习!