Go调度器是如何处理线程阻塞的

技术Go调度器是如何处理线程阻塞的本篇内容主要讲解“Go调度器是如何处理线程阻塞的”,感兴趣的朋友不妨来看看。本文介绍的方法操作简单快捷,实用性强。下面就让小编来带大家学习“Go调度器是如何处理线程阻塞的”吧!怎么让我们

本文主要解释“Go调度器如何处理线程阻塞”。感兴趣的朋友不妨看看。本文介绍的方法简单、快速、实用。让边肖带你学习“围棋程序如何处理线程阻塞”!

怎么让我们的系统更快

随着信息技术的飞速发展,单个服务器的处理能力越来越强,迫使编程模式从串行模式升级为并发模式。

并发模型包括IO复用、多进程和多线程,各有优缺点。现代复杂的高并发架构大多是几个模型一起使用,不同的场景使用不同的模型来最大化服务器的性能。

多线程因其重量轻、使用方便,成为并发编程中最常用的并发模型,后衍生协同学等其他子产品也是基于此。

并发 ne; 并行

并发 (concurrency) 和 并行 (parallelism) 是不同的。

在单个CPU核上,线程通过时间片切换任务或者放弃控制权,从而“同时运行多个任务”,这就是所谓的并发。但实际上,任何时候都只有一个任务被执行,其他任务都是通过某种算法排队的。

多核CPU可以让同一个进程中的“多个线程”同时运行,这是并行的。

进程、线程、协程

进程:进程是系统中资源分配的基本单位,具有独立的内存空间。

线程:线程是CPU调度和分派的基本单位。线程被附加到进程,每个线程将共享父进程的资源。

协同进程:协同进程是用户模式下的轻量级线程。协同过程的调度完全由用户控制。协同进程之间的切换只需要保存任务的上下文,没有内核开销。

线程上下文切换

中断处理、多任务、用户状态切换等原因都会导致CPU从一个线程切换到另一个线程。切换进程需要保存当前进程的状态,恢复另一个进程的状态。

因为上下文切换的代价是高昂的,在内核上切换线程要花很多时间。上下文切换的延迟取决于不同的因素,大约50到100纳秒。考虑到硬件在每个内核上平均每纳秒执行12条指令,上下文切换可能需要600到1200条指令的延迟时间。实际上,上下文切换占用了程序执行指令的大量时间。

如果存在跨核上下文切换,可能会导致CPU缓存失效(CPU从缓存访问数据的成本约为3到40个时钟周期,从主存访问数据的成本约为100到300个时钟周期),这种场景的切换成本会更高。

Golang 为并发而生

自2009年Golang正式发布以来,凭借其极高的运行速度和高效的开发效率,Golang迅速占领了市场份额。Golang从语言层面支持并发,通过轻量级的Goroutine实现程序的并发运行。

Goroutine 非常轻量主要体现在以下两个方面:

上下文切换成本低:Goroutine上下文切换只涉及三个寄存器(PC/SP/DX)的值修改;对比线程的上下文切换需要涉及模式切换(从用户状态到内核状态)、16个寄存器、PC、SPhellip等待寄存器刷新;

内存占用少:线程的栈空间通常是2M,Goroutine的最小栈空间是2K。

Golang程序可以轻松支持10w级的Goroutine运行,当线程数达到1k时,内存消耗已经达到2G。

Go 调度器实现机制:

Go程序使用调度器调度goroute在内核线程上执行,但goroute并不直接绑定OS线程M-Machine运行,而是Goroutine Scheduler中的P-Processor(逻辑处理器)充当“中介”获取内核线程资源。

Go scheduler模型,我们通常称之为G-P-M模型,包括四个重要的结构,即G、P、M和Sched:

G:Goroutine,每个Goroutine对应一个G结构。g存储goroutines的运行栈、状态和任务函数,可以重用。

G不是执行器,每个G都需要绑定到P才能被调度执行。

P:处理器代表逻辑处理器。对于G,P相当于CPU核心,G只有绑定到P才能调度,对于M,P提供相关的执行环境(Context),比如内存分配状态(mcache)、任务队列(G)等等。

P的个数决定了系统中并行G的最大个数(前提:物理CPU核数=P的个数)。

p的数量由用户设置的GoMAXPROCS决定,但无论GoMAXPROCS设置多大,p的最大数量都是256。

/p>

M: Machine,OS 内核线程抽象,代表着真正执行计算的资源,在绑定有效的 P 后,进入 schedule 循环;而 schedule  循环的机制大致是从 Global 队列、P 的 Local 队列以及 wait 队列中获取。

M 的数量是不定的,由 Go Runtime 调整,为了防止创建过多 OS 线程导致系统调度不过来,目前默认最大限制为 10000 个。

M 并不保留 G 状态,这是 G 可以跨 M 调度的基础。

Sched:Go 调度器,它维护有存储 M 和 G 的队列以及调度器的一些状态信息等。

调度器循环的机制大致是从各种队列、P 的本地队列中获取 G,切换到 G 的执行栈上并执行 G 的函数,调用 Goexit 做清理工作并回到  M,如此反复。

理解 M、P、G 三者的关系,可以通过经典的地鼠推车搬砖的模型来说明其三者关系:

Go调度器是如何处理线程阻塞的

地鼠(Gopher)的工作任务是:工地上有若干砖头,地鼠借助小车把砖头运送到火种上去烧制。M 就可以看作图中的地鼠,P 就是小车,G  就是小车里装的砖。

弄清楚了它们三者的关系,下面我们就开始重点聊地鼠是如何在搬运砖块的。

Processor(P):

根据用户设置的 GoMAXPROCS 值来创建一批小车(P)。

Goroutine(G):

通过 Go 关键字就是用来创建一个 Goroutine,也就相当于制造一块砖(G),然后将这块砖(G)放入当前这辆小车(P)中。

Machine (M):

地鼠(M)不能通过外部创建出来,只能砖(G)太多了,地鼠(M)又太少了,实在忙不过来,刚好还有空闲的小车(P)没有使用,那就从别处再借些地鼠(M)过来直到把小车(P)用完为止。

这里有一个地鼠(M)不够用,从别处借地鼠(M)的过程,这个过程就是创建一个内核线程(M)。

需要注意的是:地鼠(M) 如果没有小车(P)是没办法运砖的,小车(P)的数量决定了能够干活的地鼠(M)数量,在 Go 程序里面对应的是活动线程数;

在 Go 程序里我们通过下面的图示来展示 G-P-M 模型:

Go调度器是如何处理线程阻塞的

P 代表可以“并行”运行的逻辑处理器,每个 P 都被分配到一个系统线程 M,G 代表 Go 协程。

Go 调度器中有两个不同的运行队列:全局运行队列(GRQ)和本地运行队列(LRQ)。

每个 P 都有一个 LRQ,用于管理分配给在 P 的上下文中执行的 Goroutines,这些 Goroutine 轮流被和 P 绑定的 M  进行上下文切换。GRQ 适用于尚未分配给 P 的 Goroutines。

从上图可以看出,G 的数量可以远远大于 M 的数量,换句话说,Go 程序可以利用少量的内核级线程来支撑大量 Goroutine 的并发。多个  Goroutine 通过用户级别的上下文切换来共享内核线程 M 的计算资源,但对于操作系统来说并没有线程上下文切换产生的性能损耗。

为了更加充分利用线程的计算资源,Go 调度器采取了以下几种调度策略:

任务窃取(work-stealing)

我们知道,现实情况有的 Goroutine 运行的快,有的慢,那么势必肯定会带来的问题就是,忙的忙死,闲的闲死,Go 肯定不允许摸鱼的 P  存在,势必要充分利用好计算资源。

为了提高 Go 并行处理能力,调高整体处理效率,当每个 P 之间的 G 任务不均衡时,调度器允许从 GRQ,或者其他 P 的 LRQ 中获取 G  执行。

减少阻塞

如果正在执行的 Goroutine 阻塞了线程 M 怎么办?P 上 LRQ 中的 Goroutine 会获取不到调度么?

在 Go 里面阻塞主要分为一下 4 种场景:

场景 1:由于原子、互斥量或通道操作调用导致 Goroutine 阻塞,调度器将把当前阻塞的 Goroutine 切换出去,重新调度 LRQ 上的其他  Goroutine;

场景 2:由于网络请求和 IO 操作导致 Goroutine 阻塞,这种阻塞的情况下,我们的 G 和 M 又会怎么做呢?

Go 程序提供了网络轮询器(NetPoller)来处理网络请求和 IO 操作的问题,其后台通过 kqueue(MacOS),epoll(Linux)或  iocp(Windows)来实现 IO 多路复用。

通过使用 NetPoller 进行网络系统调用,调度器可以防止 Goroutine 在进行这些系统调用时阻塞 M。这可以让 M 执行 P 的 LRQ  中其他的 Goroutines,而不需要创建新的 M。有助于减少操作系统上的调度负载。

下图展示它的工作原理:G1 正在 M 上执行,还有 3 个 Goroutine 在 LRQ 上等待执行。网络轮询器空闲着,什么都没干。

Go调度器是如何处理线程阻塞的

接下来,G1 想要进行网络系统调用,因此它被移动到网络轮询器并且处理异步网络系统调用。然后,M 可以从 LRQ 执行另外的 Goroutine。此时,G2  就被上下文切换到 M 上了。

Go调度器是如何处理线程阻塞的

最后,异步网络系统调用由网络轮询器完成,G1 被移回到 P 的 LRQ 中。一旦 G1 可以在 M 上进行上下文切换,它负责的 Go  相关代码就可以再次执行。这里的最大优势是,执行网络系统调用不需要额外的 M。网络轮询器使用系统线程,它时刻处理一个有效的事件循环。

Go调度器是如何处理线程阻塞的

这种调用方式看起来很复杂,值得庆幸的是,Go 语言将该“复杂性”隐藏在 Runtime 中:Go 开发者无需关注 socket 是否是 non-block  的,也无需亲自注册文件描述符的回调,只需在每个连接对应的 Goroutine 中以“block I/O”的方式对待 socket 处理即可,实现了  goroutine-per-connection 简单的网络编程模式(但是大量的 Goroutine  也会带来额外的问题,比如栈内存增加和调度器负担加重)。

用户层眼中看到的 Goroutine 中的“block socket”,实际上是通过 Go runtime 中的 netpoller 通过  Non-block socket + I/O 多路复用机制“模拟”出来的。Go 中的 net 库正是按照这方式实现的。

场景 3:当调用一些系统方法的时候,如果系统方法调用的时候发生阻塞,这种情况下,网络轮询器(NetPoller)无法使用,而进行系统调用的  Goroutine 将阻塞当前 M。

让我们来看看同步系统调用(如文件 I/O)会导致 M 阻塞的情况:G1 将进行同步系统调用以阻塞 M1。

Go调度器是如何处理线程阻塞的

调度器介入后:识别出 G1 已导致 M1 阻塞,此时,调度器将 M1 与 P 分离,同时也将 G1 带走。然后调度器引入新的 M2 来服务  P。此时,可以从 LRQ 中选择 G2 并在 M2 上进行上下文切换。

Go调度器是如何处理线程阻塞的

阻塞的系统调用完成后:G1 可以移回 LRQ 并再次由 P 执行。如果这种情况再次发生,M1 将被放在旁边以备将来重复使用。

Go调度器是如何处理线程阻塞的

场景 4:如果在 Goroutine 去执行一个 sleep 操作,导致 M 被阻塞了。

Go 程序后台有一个监控线程 sysmon,它监控那些长时间运行的 G 任务然后设置可以强占的标识符,别的 Goroutine  就可以抢先进来执行。

只要下次这个 Goroutine 进行函数调用,那么就会被强占,同时也会保护现场,然后重新放入 P 的本地队列里面等待下次执行。

到此,相信大家对“Go调度器是如何处理线程阻塞的”有了更深的了解,不妨来实际操作一番吧!这里是网站,更多相关内容可以进入相关频道进行查询,关注我们,继续学习!

内容来源网络,如有侵权,联系删除,本文地址:https://www.230890.com/zhan/103661.html

(0)

相关推荐

  • 刷墙面漆步骤,房子的墙面怎么刷涂料,详细步骤

    技术刷墙面漆步骤,房子的墙面怎么刷涂料,详细步骤墙面刷涂料的粉刷大致分为以下几个步骤:1.处理墙面基层刷墙面漆步骤;2.涂刷界面剂;3.防水处理;4.刮腻子;5.砂纸打磨;6.刷油漆。 1. 处理墙面基层
    墙面基层处理

    生活 2021年10月25日
  • 工资核算方法,HR计算月工资方式怎么样才正确

    技术工资核算方法,HR计算月工资方式怎么样才正确工资计算工资核算方法,要根据员工的工资结构来算。
    最基本的工资结构为无责底薪+绩效奖金。
    如果是销售岗位,会变为无责底薪+绩效奖金+业绩提成。
    有的公司福利好点,还会改为无

    生活 2021年10月22日
  • 一筹莫展什么意思,一筹莫展的筹是什么意思

    技术一筹莫展什么意思,一筹莫展的筹是什么意思◎ 一筹莫展 yīchóu-mòzhǎn筹:计策。展:施展。一点计策也想不出;一点儿办法也没有《宋史·蔡幼学传》:“多士盈庭而一筹不吐。”后以“一筹莫展”比喻一点办法也没有。

    生活 2021年10月29日
  • 奶牛的英文,英文单词“cow”的读音是什么

    技术奶牛的英文,英文单词“cow”的读音是什么英式读音[kaʊ] 美式读音[kaʊ] n. 奶牛奶牛的英文,乳牛;母兽;雌(象,鲸等);对女性的蔑称; vt. 恐吓;吓唬;威胁; ①造句:He kep

    生活 2021年10月22日
  • Django ManyToManyField 跨越中间表查询的方法是什么

    技术Django ManyToManyField 跨越中间表查询的方法是什么本篇文章为大家展示了Django ManyToManyField 跨越中间表查询的方法是什么,内容简明扼要并且容易理解,绝对能使你眼前一亮,通过

    攻略 2021年11月30日
  • mysql5.7无法开启二进制日志的示例分析

    技术mysql5.7无法开启二进制日志的示例分析小编给大家分享一下mysql5.7无法开启二进制日志的示例分析,相信大部分人都还不怎么了解,因此分享这篇文章给大家参考一下,希望大家阅读完这篇文章后大有收获,下面让我们一起

    攻略 2021年11月6日