浅谈go协程及其调度模型
每当我看着大海的时候,我总想找人谈谈。但当我和人交谈时,我又总想去看看大海。 —— 村上春树
go在语言层面对协程进行了原生的支持并且称为goroutine,这也是go语言强大并发能力的重要支撑。
本文并非一蹴而就,而是蓄势待发已久。
后续随着知识及理解的不断深入仍将持续补充进来。同时也欢迎各位留言探讨,互相学习。
目录
进程、线程、协程
为什么会选择协程
goroutine调度模型-GPM
并发与并行
进程、线程、协程
1,进程是系统资源分配的最小单位,线程是CPU调度的最小单位;
2,进程是线程的载体,一个进程可由多个线程组成,进程内的多个线程间可以相互通信;
3,进程的创建和销毁都是系统资源级别,因此是一种比较昂贵的操作;
4,线程是抢占式调度,协程是协作式调度,多线程无可避免的会带来频繁的CPU上下文切换,调度成本高,但抢占式调度由系统内核来完成的,用户态不需要参与,内核参与使得平台移植好;协作式调度中用户态协程会主动让出CPU控制权来让其他协程使用;
5,协程不和内核交互,协程是用户态轻量级线程;
6,线程与协程可简单的理解为捆绑关系,任意数量的用户态协程可以运行在任意数量的os线程上;用户创建的协程会被专门负责管理goroutine的processor接管调度,一个processor可对应N个协程;
7,原生的linux线程从进程栈分配空间,所分配的线程栈空间大小默认为8M:
而创建一个协程分配的空间是2k,空间可能会自动扩增。
为什么会选择协程
原生线程的调度成本高,那么go语言自己进行了接管调度---go runtime进行调度。
goroutine调度模型-GPM
源码位置:src/runtime/proc.go
// Goroutine scheduler
// The scheduler's job is to distribute ready-to-run goroutines over worker threads.
//
// The main concepts are:
// G - goroutine.
// M - worker thread, or machine.
// P - processor, a resource that is required to execute Go code.
// M must have an associated P to execute Go code, however it can be
// blocked or in a syscall w/o an associated P.
G:是对Goroutine的抽象。其中包括执行的函数指令及参数,G保存的任务对象、线程上下文切换,现场保护和现场恢复需要的寄存器(SP、IP)等信息,源码中runtime.g 结构表示的就是G。
M:os线程,即操作系统内核线程,也可以叫物理processor。源码中runtime.m 结构表示的就是M。通过M来找寻空闲的P从而给其下面挂需要执行的G;
M最大数量10000个(见源码proc.go的schedinit()函数:sched.maxmcount = 10000)。
P:执行go代码所需的处理器,是负责golang运行时的线程,负责执行goroutine,真正决定并发程度的是P,最大值为256个。初始化的时候一般会去读取GOMAXPROCS对应的值,如果没有显示设置,则会读取默认值。在Go1.5之后GOMAXPROCS被默认设置可用的核数,而之前则默认为1,源码中runtime.p 结构表示的就是P。
全局队列:维护一个队列用来存放等待执行的G,新创建的goroutine会先存放在本地队列,如果本地队列满了,则会将本地队列的G移动到全局队列里面,本地队列保存G数量默认不超过256个。所有M共享P全局队列。
调度器:Goroutine调度器中的P和OS调度器通过M相结合,操作系统调度器负责把内核线程分配到CPU的核上执行。当P有任务时需要创建或者唤醒一个系统线程来执行它队列里的任务,所以P和M需要进行绑定,所有的G实质上在M上执行。
每个P对应着一个M(线程想运行任务就需要先获取P),即M必须具有关联的P才能调度goroutine。新创建的goroutine会先存放在P的本地队列(图中灰色部分的G所在队列)中,等待调度器进行调度。
M与P绑定后,M先从P的运行队列(图中浅蓝色部分的G所在的待运行队列)中取出G,并切换到G的堆栈执行,当P的本地队列中没有G时,再从本地待调度队列中获取一个G分配给这个P,当本地队列中也没有待运行的G时,则尝试从其它的P偷部分G来执行(偷的时候偷一半)。
通过这种模型,go避免了大量的上下文切换。
——引入:如何减少上下文切换?(参考上下文切换 - Go语言中文网 - Golang中文社区)
- 无锁并发编程,锁的获取与释放会发生上下文切换,多线程时会影响效率。无锁并发编程就是将数据分块,每个线程处理各自模块。比如LongAdder中部分代码。
- CAS算法,并发编程时通过CAS算法更新数据,而不必加锁。如Java的atomic包下的工具类。
- 使用最少线程,减少不必要的线程创建,自定义线程池。
- 使用协程,在单线程中维护多任务调度,处理任务间切换,Golang对于协程的使用貌似很强大。
调度执行与现场恢复:M执行过程中随时会发生上下文切换。当发生上线文切换时需要对执行现场进行保护,以便下次被调度执行时进行现场恢复。Go调度器M的栈保存在G对象上,只需要将M所需要的寄存器(SP、PC等)保存到G对象上就可以实现现场保护。当这些寄存器数据被保护起来就随时可以做上下文切换了,在中断之前把现场保存起来。如果此时G任务还没有执行完,M可以将任务重新丢到P的任务队列,等待下一次被调度执行。当再次被调度执行时,M通过访问G的vdsoSP、vdsoPC寄存器进行现场恢复(从上次中断位置继续执行)。
源码对应
M:
type m struct {g0 *g // goroutine with scheduling stackmorebuf gobuf // gobuf arg to morestackdivmod uint32 // div/mod denominator for arm - known to liblink// Fields not known to debuggers.procid uint64 // for debuggers, but offset not hard-codedgsignal *g // signal-handling ggoSigStack gsignalStack // Go-allocated signal handling stacksigmask sigset // storage for saved signal masktls [6]uintptr // thread-local storage (for x86 extern register)mstartfn func()curg *g // current running goroutinecaughtsig guintptr // goroutine running during fatal signalp puintptr // attached p for executing go code (nil if not executing go code)nextp puintptroldp puintptr // the p that was attached before executing a syscallid int64mallocing int32throwing int32preemptoff string // if != "", keep curg running on this mlocks int32dying int32profilehz int32spinning bool // m is out of work and is actively looking for workblocked bool // m is blocked on a notenewSigstack bool // minit on C thread called sigaltstackprintlock int8incgo bool // m is executing a cgo callfreeWait uint32 // if == 0, safe to free g0 and delete m (atomic)fastrand [2]uint32needextram booltraceback uint8ncgocall uint64 // number of cgo calls in totalncgo int32 // number of cgo calls currently in progresscgoCallersUse uint32 // if non-zero, cgoCallers in use temporarilycgoCallers *cgoCallers // cgo traceback if crashing in cgo callpark notealllink *m // on allmschedlink muintptrmcache *mcachelockedg guintptrcreatestack [32]uintptr // stack that created this thread.lockedExt uint32 // tracking for external LockOSThreadlockedInt uint32 // tracking for internal lockOSThreadnextwaitm muintptr // next m waiting for lockwaitunlockf func(*g, unsafe.Pointer) boolwaitlock unsafe.Pointerwaittraceev bytewaittraceskip intstartingtrace boolsyscalltick uint32freelink *m // on sched.freem// these are here because they are too large to be on the stack// of low-level NOSPLIT functions.libcall libcalllibcallpc uintptr // for cpu profilerlibcallsp uintptrlibcallg guintptrsyscall libcall // stores syscall parameters on windowsvdsoSP uintptr // SP for traceback while in VDSO call (0 if not in call)vdsoPC uintptr // PC for traceback while in VDSO call// preemptGen counts the number of completed preemption// signals. This is used to detect when a preemption is// requested, but fails. Accessed atomically.preemptGen uint32dlogPerMmOS
}
并发与并行
文末,顺带聊聊并发与并行
并行:你在吃饭,来了个电话,无关紧要,你一边吃一边说;
并发:你在吃饭,来了个电话,因为一些因素必须通完电话才能继续吃饭(你的身体先由打电话支配使用),通完电话后继续吃饭(身体切换到可以吃饭)。
浅谈go协程及其调度模型相关推荐
- swoole一键携程化mysql_【SWOOLE系列】浅淡SWOOLE协程(二) 一键协程化
前言 是的,我又来了,我带着我的文章表情包回来. 再这感谢swoole大佬们的点赞和转载,让我短暂的感受到了什么要叫高光时刻. 背景 我相信大部分人一开始用swoole的协程的时候都会再协程里写了一大 ...
- 线程/协程/异步的编程模型(CPU利用率为核心)
最近看了一个b站博主的视频https://www.bilibili.com/video/av64066246/讲到了线程/协程/异步的编程模型,这里做下记录 1.线程 上篇文章有聊到进程和线程的关系, ...
- python多线程调度_python并发编程之进程、线程、协程的调度原理(六)
进程.线程和协程的调度和运行原理总结. 系列文章 进程.线程的调度策略介绍 linux中的进程主要有三种调度策略: 优先级调度:将进程分为普通进程和实时进程: 先进先出(队列)调度:实时进程先创建的先 ...
- python并发编程-进程池线程池-协程-I/O模型-04
目录 进程池线程池的使用***** 进程池/线程池的创建和提交回调 验证复用池子里的线程或进程 异步回调机制 通过闭包给回调函数添加额外参数(扩展) 协程*** 概念回顾(协程这里再理一下) 如何实现 ...
- 计算机网络:浅谈HTTP与TCP/IP四层模型
计算机网络:浅谈HTTP与TCP/IP四层模型 其实对于我这样的非科班出身来说,理解算法不是最难的.最难的就是计算机网络这种无法一口吃完的内容.因此专门抽空写点这方面的内容.其实本来只是想写一篇HTT ...
- 十、Go协程的调度,互斥锁,计数器和线程池
@Author:Runsen 在字节面试中,我见过:GO语言中的协程与Python中的协程的区别?其实就是要我讲解Go中GMP机制.我表示很多都用过,但是底层不了解. 那时我只知道与传统的系统级线程和 ...
- Cpython解释器下实现并发编程——多进程、多线程、协程、IO模型
一.背景知识 进程即正在执行的一个过程.进程是对正在运行的程序的一个抽象. 进程的概念起源于操作系统,是操作系统最核心的概念,也是操作系统提供的最古老也是最重要的抽象概念之一.操作系统的其他所有内容都 ...
- python(40)- 进程、线程、协程及IO模型
一.操作系统概念 操作系统位于底层硬件与应用软件之间的一层.工作方式:向下管理硬件,向上提供接口. 操作系统进行进程切换:1.出现IO操作:2.固定时间. 固定时间很短,人感受不到.每一个应用层运行起 ...
- 5,线程池,进程池,协程,IO模型
今日内容: 1,线程池 2,进程池 3,协程 4,IO 模型 服务端要满足这三个条件: 1,24小时不间断的提供服务 2,能够支持高并发 3,要有固定的IP地址和端口在服务端这个地方会出现阻塞态情况: ...
最新文章
- 一个理想主义者关于爱情和美女、事业与金钱的疯人痴语
- 问题解决java.lang.IllegalArgumentException at org.springframework.asm.ClassReader
- Spring WebClient vs. RestTemplate
- python dicom 器官分割_图像识别 | 使用Python对医学Dicom文件的预处理(含代码)
- Pandas库(2):数据的统计分析
- 使用 SASS Mixin 编写 clean code
- power bi 日期计算_PowerBI 动态计算周内日权重指数
- Android Service、IntentService,Service和组件间通信
- 熊仔科技Steamduino PIC18F46J50主控板 部分原理图
- WPF自定义控件与样式(5)-Calendar/DatePicker日期控件自定义样式及扩展
- PowerPC E500 MMU详解
- Android Studio 初探
- linear-gradient 立体背景 按钮_2020高邮亮光背景墙8D立体逼真方兴装饰了解更多
- 关于DBC文件的创建(DBC文件系列其一)
- 排错万能金钥匙之Linux系统应用
- 【操作系统-进程】PV操作——哲学家问题
- mysql dede arctiny_dede标签的使用
- 相机对焦和调焦距的区别是什么 ?
- 蓝色荧光染料Monobromobimane (mBBr) 单溴二胺71418-44-5
- 包头新松机器人_煤矿机器人现状及发展方向