2024年最新Go分布式爬虫笔记(十七) 4月Day1_go 本地线程变量

另外,每一个 M 结构中都存储了一个特殊的协程 g0,协程 g0 运行在操作系统的线程栈上,它的主要作用是执行协程调度的一系列运行时代码,一般的协程则负责无差别地执行用户代码。

很显然,执行用户代码的任何协程都不适合进行全局调度。当用户协程退出或者被抢占时,意味着需要重新执行协程调度,这时,我们需要从用户协程 g 切换到协程 g0,这样才能完成协程的调度。

image

协程经历从 g→g0→g 的过程之后,就完成了一次调度循环。和线程类似,协程切换的过程叫作协程的上下文切换。

当某一个协程 g 执行上下文切换时,需要保存当前协程的执行现场,才能够在后续切换回 g 协程时正常执行。协程的执行现场存储在 g.gobuf 结构体中,g.gobuf 结构体主要保存 CPU 中几个重要的寄存器值,分别是 rsp、rip、rbp。

type gobuf struct {
  // 保存CPU 的rsp 寄存器的值
  sp uintptr
  // 保存CPU 的rip 寄存器的值
  pc uintptr
  // 记录当前这个gobuf 对象属于哪个Goroutine
  g guintptr
  // 保存系统调用的返回值
  ret sys.Uintreg
  // 保存CPU 的rbp 寄存器的值
  bp uintptr
  ...
}

调度循环

从协程 g0 调度到协程 g:

  • schedule 函数: 处理的是具体的调度策略,也就是选择下一个要执行的协程
  • execute 函数: 执行的是一些具体的状态转移、协程 g 与结构体 m 之间的绑定等操作
  • gogo 函数;与操作系统有关的函数,用于完成栈的切换以及恢复 CPU 寄存器。

执行完这一步之后,协程就会切换到协程 g 去执行,当协程 g 主动让渡、被抢占或退出后,又会切换到协程 g0 开始下一轮调度。

在从协程 g 切换回协程 g0 时,mcall 函数会保存当前协程的执行现场,mcall 函数是和平台有关的汇编指令。

协程切换到 g0 后,根据切换原因的不同,会执行不同的函数。

  • 如果是用户调用 Gosched 函数主动让渡执行权,就会执行 gosched_m 函数;
  • 如果协程已经退出,则执行 goexit 函数,将协程 g 放入 p 的 freeg 队列,方便下次重用。

执行完毕后,运行时再次调用 schedule 函数开始新一轮的调度循环,从而形成一个完整的闭环,循环往复。

image

调度算法

调度的核心策略位于 schedule 函数中。

// runtime/proc.gofunc 
schedule() {
  ...
}

由于程序中不可能同时执行成千上万个协程,因此,那些等待被调度的协程就存储在了运行队列中。

Go 语言调度器将运行队列分为局部运行队列与全局运行队列。

  • 局部运行队列是每个 P 特有的长度为 256 的数组。这个数组模拟了一个循环队列
type p struct {
  // 使用数组实现的循环队列
  runq [256]guintptr
  runnext guintptr
}

+ runqhead 标识了循环队列的开头
+ runqtail 标识了循环队列的末尾。
+ 每次将 G 放入本地队列时,都是从循环队列的末尾插入,而获取 G 时则是从循环队列的头部获取。
+ 除此之外,在每个 P 内部还有一个特殊的 runn
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值