Go语言自带了一个高效的协程调度器,可以轻松地处理并发任务,实现高性能的程序。在本文中,我们将深入了解Go语言中的协程调度器,并探讨其实现、操作以及优化。
协程简介
协程是一种轻量级的线程或者称之为用户态线程。它由程序员调度,而不是由操作系统调度。协程的特点是非抢占式,即只有在显式调用yield()函数时才会切换上下文。因此,协程的切换开销非常小,可以轻松地创建和销毁,而且可同时运行非常多的协程,以实现并发执行程序。
Go语言协程模型
Go语言采用的是M:N 协程模型,也就是多个用户态线程 M,对应多个系统级线程 N 的关系。这种模型充分发挥了多核CPU的优势,同时减少了上下文的切换开销,提高了调度性能。
M表示操作系统线程,即物理线程,是操作系统调度的最小单位。而N则表示Go语言运行时系统(runtime)中的虚拟线程(goroutine),是实现并发的最小单位。N个goroutine 会映射到M个线程上,在运行时由调度器调度。
协程调度器
协程调度器是Go语言运行时系统中的核心组件,负责管理并调度多个协程执行任务。它是一个高级别的调度器,可以控制协程的运行和切换,实现协程的级别调度。在Go语言中,协程调度器额外使用了一种成为Goroutine的运行实体,可以更加高效地在协程中切换执行任务。
协程调度器实现原理
协程调度器实现的原理可以分为两个层次:操作系统层面和Go语言运行时系统层面。
操作系统层面
操作系统层面上,协程调度器会在运行时映射多个用户线程到多个操作系统线程上,利用多核CPU的并行计算能力。
Go语言协程模型中的M:N架构,即M表示操作系统线程(Machine),N表示Go语言的虚拟线程(N,代表Goroutine),在运行时由调度器管理调度。调度器的主要作用就是在每个操作系统线程上维护一个调度任务队列,根据任务队列中任务的优先级和调度算法动态调度各个线程上的任务的执行,并管理线程资源。
Go语言运行时系统层面
在Go语言运行时系统层面上,协程调度器使用了三种机制:调度器、调度器队列和P。
调度器
Go语言的协程调度器由一个全局的调度器控制,它会维护调度器队列、P队列、自旋次数、调度算法等。调度器会动态管理每个线程上的任务执行,以实现优化协程的执行效率。
调度器队列
调度器队列是调度器用来记录所有等待调度的 Goroutine 的地方。在调度器将 Goroutine 分配到 P 上时,它会首先从队列中寻找等待调度的 Goroutine,如果找到了,则立即把它们放入 P 的本地队列,如果没有找到,则新创建一个Goroutine并放到P的本地队列中。
P
P 是一个处理器,用来执行 Goroutine,它所拥有的队列就是本地队列。P 的数量由 GOMAXPROCS 环境变量所控制,如果不设置,Go程序默认使用机器的核心数。
Go调度器的优化
Go调度器有许多优化策略,以下是其中几个:
当某个P队列内的 Goroutine中都被阻塞时,Go调度器会查找其它P队列中的 Goroutine然后偷走一些放到自己队列去运行。这种策略保证了所有P的稳定负载和均负载。
Goroutine可以在执行的时候给自己设置一个定时器,当时间到了之后调用 runtime.Goexit,告诉 runtime 这个 Goroutine 可以主动调度了。如果 goroutine 没有主动地调用 Goexit,Go调度器也有一种预抢占策略。在 Go 1.13 之前,只有在 GOMAXPROCS 为 1 的时候才能实现预抢占。现在已经升级支持更多情况下。
为了减少不同线程之间任务的争抢,每个线程都会有一个自己的本地队列,每个线程优先从本地队列中取任务执行,只有本地队列为空时才会去全局队列中获取任务。
总结
协程调度器是Go语言高性能并发的关键组件之一。它负责管理并调度多个协程执行任务,采用的是M:N协程模型,在操作系统层面和Go语言运行时系统层面使用不同的机制和算法来保证协程的高效执行和调度。同时,Go调度器还有许多优化策略,比如协程抢占、局部运行队列以及任务窃取等,可以帮助程序更加高效地运行,提高程序性能和吞吐量。
以上是Go语言中的协程调度器详解的详细内容。更多信息请关注PHP中文网其他相关文章!