【译】Golang中的调度(2):Go调度器 - Go Scheduler

本文深入探讨了Go语言的调度器机制,重点在于协作式调度、Goroutine的状态以及上下文切换。Go调度器并非抢占式,而是协作式的,这意味着调度决策取决于代码中的安全点,如函数调用。Goroutine有等待、就绪和运行三种状态。上下文切换发生在函数调用、系统调用、垃圾回收和同步操作等事件中。此外,文章还介绍了异步和同步系统调用的处理方式,以及工作偷窃策略,以保持所有处理器的高效利用。
摘要由CSDN通过智能技术生成

为了更好理解Go调度器的内在机制,我会以三个部分的内容分别进行阐述,链接如下:

  1. Golang中的调度(1):OS调度器 - OS Scheduler
  2. Golang中的调度(2):Go调度器 - Go Scheduler
  3. Golang中的调度(3):并发- Concurrency

本部分内容主要讨论Go调度器。

引言

在本系列文章的第一部分,我阐述了在OS调度器中各个方面的知识,这些内容对于理解与欣赏Go调度器的机制是非常重要的。在本部分内容,我将在语义级别上阐述Go调度器的工作模式,并专注于较高视角下的行为。Go调度器是一个复杂的系统,一些细节并不重要,重要的是要有一个良好的模型来表明调度器的工作方式和行为表象,这将有助你作出更好的工程决策。

开始你的程序

当你的Go程序启动时,主机会为每个标识了的虚拟内核分配一个逻辑处理器(P)。如果你的处理器每个物理内核具有多个硬件线程(Hyper-Threading超线程),而每个硬件线程将会作为一个虚拟内核提供给你的Go程序使用。为了更好的理解这一点,提供一下我的MacBook Pro的系统报告。

图 1

你可以看到我的Mac有一个四内核的处理器。但该报告中并未公开本Mac上每个物理内核上的硬件线程数。英特尔酷睿i7处理器具有超线程技术,这意味着每个物理内核具有两个硬件线程。这意味着当并行执行操作系统线程时,将有8个虚拟内核提供给Go程序使用。为了验证这一点,请看以下程序:

表 1

package main

import (
	"fmt"
	"runtime"
)

func main() {

    // NumCPU returns the number of logical
    // CPUs usable by the current process.
    fmt.Println(runtime.NumCPU())
}

当在我本机电脑运行以上程序时,NumCPU()函数的执行结果是8。在我电脑上运行的任何Go程序都会是8个处理器(8 P’s)。

每个P都分配有一个操作系统线程(M),该线程由操作系统管理,并且操作系统仍负责将线程置于内核上以执行,正如上部分内容所诉。这意味着当在我的机器上运行一个Go程序,我将能有8个线程去执行任务,每一个都分别对应到一个P上。

每个Go程序启动时都会有一个初始Goroutine(G),它是Go程序的执行路径。一个Goroutine从本质上讲,它是一个协程(Coroutine)。但这是Go,因此我们用G字母代替了字母C,并且使用了新的单词Goroutine。你可以将Goroutine视为应用程序级的线程,同时它与操作系统线程在很多方面也很类似。就像操作系统线程的上下文切换在内核上,Goroutine的上下文切换是在M上。

最后一块让人困惑的是运行队列。在Go调度器中有两个不同的运行队列:全局运行队列Global Run Queue(GRQ)和本地运行队列Local Run Queue(LRQ)。每个P都有一个LRQ,该LRQ管理分配给P的上下文中执行的Goroutines。这些Goroutine被分配给P的M轮流进行上下文切换。GRQ指的是尚未分配给某个P的Goroutines。从GRQ转移到LRQ的过程,我们将在稍后进行讨论。

图2提供了所有上述这些模型组件的图形表示。

图 2

协作式调度器

正如我们在第一部分讨论的一样,OS调度策略是抢占式的。从本质上,这意味着你无法在任何给定时间上预测调度器将要执行的操作。内核在做决策,但这一切都是不确定的。运行在操作系统之上的程序无法通过调度

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值