> 文档中心 > 【Go实战基础】轻量级线程 goroutine

【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 

 菜鸟实战,持续学习!