ICode9

精准搜索请尝试: 精确搜索
首页 > 其他分享> 文章详细

GMP模型

2022-07-03 13:34:14  阅读:197  来源: 互联网

标签:执行 协程 队列 模型 调度 线程 GMP CPU


Golang协程调度器原理 & GMP设计思想

地址:https://www.bilibili.com/video/BV19r4y1w7Nx

1.Golang调度器的由来

1.1 早期单进程操作系统

所有进程只能顺序执行,产生问题:

  • 单一执行流程,计算机只能一个任务一个任务进行
  • 进程阻塞导致CPU浪费 (即某个进程阻塞,会导致后面程序无法继续执行)

1.2 多进程/多线程操作系统

​ 以时间片轮询的机制并发执行程序 (并行与CPU核数有关),很显然多线程/多进程可以解决1.1中进程阻塞导致CPU浪费的问题(即是你程序阻塞,时间片到了,也会强制释放CPU)。产生问题:

  • CPU高消耗

    ​ 以时间片轮询机制而言,为了保存原有线程的系统调用或者相关资源环境等,必然涉及到拷贝复制的过程,就会涉及到切换成本,造成CPU浪费。因此线程越多,切换过程就会更加频繁,所以线程并不是越多越好(线程越多抢占CPU会越有利)。

  • 内存高占用

    在32bit操作系统中,一个进程占用虚拟内存4GB;而一个线程占用4MB左右;因此多进程/多线程会导致内存高占用的问题。(而协程占用KB级别)

1.3 协程

​ 为了解决1.2的CPU高消耗和内存高占用问题,而内核态无法修改,所以尝试修改用户态。将线程分为用户线程和内核线程。而内核线程称为线程,用户线程称为协程,由于CPU视野只有内存空间,因此协程的开辟对CPU来说是无感的

​ 线程通过协程调度器绑定多个协程,而CPU视野只有内核空间,所以对CPU而言只有单一线程即进程,因此此方法可有效解决CPU高消耗的问题

​ 每个语言对协程进行不同处理。Golang对协程进行相应优化:对协程co-routine重命名为goroutine;修改协程内存大小,每个goroutine只有几KB大小,因此可以大量创建;可灵活调度,切换成本较低。所以最后重点就落到了优化协程调度器上面。

2.协程调度器和GMP模型设计思想

2.1 早期协程调度器

​ 各个线程首先需要去全局G队列拿锁,才能去执行协程挂载的任务,此时该线程不释放锁就导致其他线程无法去执行协程上的任务。

缺点:

  • 创建,销毁,调度协程都需要先去获取锁,这就导致形成了激烈的锁竞争
  • CPU在线程之间频繁切换会增加系统开销;

2.2 GMP模型简介

G ------ goroutine协程
P ------ 协程调度器
M ------ 线程

​ 每个 P 保存了当前执行的协程G内部资源信息(堆栈地址和变量参数等),所以M要先去获取P才能去执行G。创建的G会优先存放在本地队列,如果本地队列满了(最多256个G),会存放至全局G队列。

P 的个数,可由环境变量中$GOMAXPROCS设置;或在程序中可通过runtime.GOMAXPROCS()设置。

2.3 调度器设计策略

2.3.1 复用线程

复用线程可避免创建与销毁线程中进行的资源消耗;

实现的两种机制:

  • work stealing机制

    ​ 当线程M1P绑定,正在执行协程G1,而此时线程M2空闲,此时M2的协程调度器P会从M1的本地协程队列中偷取协程G到自己这边执行。

  • hand off机制

    ​ 当此时M1M2正常执行协程所挂载的任务,突然协程G1发生阻塞现象(比如read/write/channel阻塞等),这时系统会尝试唤醒/创建一个线程M3(优先唤醒,符合复用线程的思想),并把与当前阻塞线程M1绑定的协程调度器P转移到新的线程M3上,并把原来阻塞线程M1所占用的CPU进行释放。后续执行完成后,如果G1需要执行会被重新加入队列进行执行,M1会被睡眠或者销毁。

2.3.2 利用并行

​ 可充分发挥多核优势,通过设置GOMAXPROCS设置协程调度器的个数,通常并不会挂满,设置为CPU核数/2。

2.3.3 抢占

​ 相较于老的调度器而言,老调度器中只有当当前协程释放CPU,另一个协程才去执行;现在调度器以时间片而言,一个时间片到了后会强制释放CPU给其他协程使用。

2.3.4 全局G队列

​ 当线程空闲时,会首先从其他线程对应协程本地队列偷取(即work stealing机制),如果偷不到,会从全局G队列进行获取(前提要先去获取锁)。

2.4 "go func()"的历程

开始:

  • 1.执行go func() 会创建一个协程G
  • 2.创建的G优先会被调度到创建G线程对应的本地队列,如果本地队列已满,则G会被加入到全局队列;
  • 3.线程M会通过协程调度器P获取协程G执行。执行go func()之前,如果本地队列为空,优先会从其他线程对应的本地队列偷取G执行,即是work sealing机制;若其他线程对应的本地队列为空,则会从全局队列获取G进行执行;
  • 4.以时间片循环执行go func()对应的执行代码;即上图中(4-调度,5-执行,6-时间片返回,时间片到了会重新加入到本地队列)

如执行go func()代码产生阻塞现象:

为了节省资源,提高CPU利用率

  • 5.系统首先会从本地休眠线程队列中唤醒一个线程M来接管当前正阻塞的线程对应的P和本地G队列;如果本地休眠线程队列没有,则会新创建一个线程M,即hand off 机制;阻塞协程G会和当前线程M进行绑定;

  • 6.当阻塞完成后,线程M会被加入到休眠线程队列或者被销毁掉,而G则会被加入到其他本地队列,如果本地队列都满了,则会被加入到全局队列;

2.5 调度器的生命周期

`

2.6 可视化的GMP编程

地址:https://www.bilibili.com/video/BV19r4y1w7Nx?p=7&spm_id_from=pageDriver

标签:执行,协程,队列,模型,调度,线程,GMP,CPU
来源: https://www.cnblogs.com/wustjq/p/16439702.html

本站声明: 1. iCode9 技术分享网(下文简称本站)提供的所有内容,仅供技术学习、探讨和分享;
2. 关于本站的所有留言、评论、转载及引用,纯属内容发起人的个人观点,与本站观点和立场无关;
3. 关于本站的所有言论和文字,纯属内容发起人的个人观点,与本站观点和立场无关;
4. 本站文章均是网友提供,不完全保证技术分享内容的完整性、准确性、时效性、风险性和版权归属;如您发现该文章侵犯了您的权益,可联系我们第一时间进行删除;
5. 本站为非盈利性的个人网站,所有内容不会用来进行牟利,也不会利用任何形式的广告来间接获益,纯粹是为了广大技术爱好者提供技术内容和技术思想的分享性交流网站。

专注分享技术,共同学习,共同进步。侵权联系[81616952@qq.com]

Copyright (C)ICode9.com, All Rights Reserved.

ICode9版权所有