golang GMP
2021-11-19 09:39:38 3 举报
AI智能生成
Golang GMP(Go Multiple Precision arithmetic)是一个用于处理高精度算术运算的库。它提供了一组数据类型和操作符,可以用于执行任意精度的整数和浮点数计算。GMP的主要优势在于其高效的算法和内存管理,使得在处理大量数据时能够保持较低的性能开销。 GMP支持多种编程语言,包括C、C++、Python等,但在Golang中使用时,需要通过cgo调用C语言实现的GMP库。这使得在Golang中使用GMP相对简单,只需引入相应的包即可。 总之,Golang GMP是一个功能强大且易于使用的高精度算术运算库,适用于需要在Golang中进行大量数值计算的场景。
作者其他创作
大纲/内容
OS调度
基本概念
以线程为调度的基本单位,利用调度算法来进行明智的决策,从而减少调度延迟
线程
线程的状态
Waiting
这意味着线程已停止并等待某些事情才能继续。这可能是因为等待硬件(磁盘、网络)、操作系统(系统调用)或同步调用(原子、互斥)等原因。这些类型的延迟是性能不佳的根本原因。
Runable
这意味着线程需要内核上的时间,以便它可以执行分配给它的机器指令,如果你有很多线程需要时间,那么线程必须等待更长的时间才能获得时间。此外,随着更多线程争夺时间,任何给定线程获得的单独时间都会缩短。这种类型的调度延迟也可能是导致性能不佳的原因
Running
这意味着线程已放置在核心上并正在执行其机器指令。按照每个程序希望的,应用程序正在完成相关工作。
线程的工作方式
CPU密集型
这项工作永远不会造成线程可能处于等待状态的情况. 这是一项不断进行计算的工作
IO密集型
这是导致线程进入等待状态的工作。这项工作包括通过网络请求访问资源或对操作系统进行系统调用。如我们访问数据库,进行同步事件(互斥体、原子),这会导致线程进入等待。
线程的上下文切换
基本概念
在CPU核心上交换线程的物理行为称为上下文切换。发生上下文切换是在当调度程序从内核中取出一个 Executing 线程并用一个 Runnable Thread 替换它时。从运行队列中选择的线程进入 Executing 状态,被取出的线程可以移回 Runnable 状态(如果它仍然具有运行能力),或进入 Waiting 状态(如果由于 IO-Bound 类型的请求而被替换)。
缺点
上下文切换资源消耗严重,因为在内核上和内核外交换线程需要时间。上下文切换过程中产生的延迟量取决于不同的因素,但花费约1000到约1500纳秒的时间。硬件在平均情况下,在每个核上每纳秒执行12条指令,上下文切换可能会花费约12k到约18k条指令的延迟。本质上,您的程序在上下问切换时正在失去大量执行指令的能力
goroutine
概念
Go 语言调度器中待执行的任务,它在运行时调度器中的地位与线程在操作系统中差不多,但是它占用了更小的内存空间,也降低了上下文切换的开销。Goroutine 只存在于 Go 语言的运行时,它是 Go 语言在用户态提供的线程,作为一种粒度更细的资源调度单元,如果使用得当能够在高并发的场景下更高效地利用机器的 CPU
优势
内存占用小 一个G 2KB左右,可以大量开辟
可以灵活调度,调度切换开销小
状态
_Gidle
刚刚被分配并且还没有被初始化
_Grunnable
没有执行代码,没有栈的所有权,存储在运行队列中
_Grunning
可以执行代码,拥有栈的所有权,被赋予了内核线程 M 和处理器 P
_Gsyscall
正在执行系统调用,拥有栈的所有权,没有执行用户代码,被赋予了内核线程 M 但是不在运行队列上
_Gwaiting
由于运行时而被阻塞,没有执行用户代码并且不在运行队列上,但是可能存在于 Channel 的等待队列上
_Gdead
没有被使用,没有执行代码,可能有分配的栈
_Gcopystack
栈正在被拷贝,没有执行代码,不在运行队列上
_Gpreempted
由于抢占而被阻塞,没有执行用户代码并且不在运行队列上,等待唤醒
_Gscan
GC 正在扫描栈空间,没有执行代码,可以与其他状态同时存在
状态切换
状态简化(就跟线程一样了)
waiting
Goroutine 正在等待某些条件满足,例如:系统调用结束等,包括 _Gwaiting、_Gsyscall 和 _Gpreempted 几个状态
runnable
Goroutine 已经准备就绪,可以在线程运行,如果当前程序中有非常多的 Goroutine,每个 Goroutine 就可能会等待更多的时间,即 _Grunnable;
running
Goroutine 正在某个线程上运行,即 _Grunning;
调度器
历史版本
单线程调度器 0.x
只包含 40 多行代码;
程序中只能存在一个活跃线程,由 G-M 模型组成
多线程调度器 1.0
允许运行多线程的程序;
全局锁导致竞争严重
任务窃取调度器 1.1
引入了处理器 P,构成了目前的 G-M-P 模型;
在处理器 P 的基础上实现了基于工作窃取的调度器;
在某些情况下,Goroutine 不会让出线程,进而造成饥饿问题;
时间过长的垃圾回收(Stop-the-world,STW)会导致程序长时间无法工作
抢占式调度器 1.2-至今
基于协作的抢占式调度器 - 1.2 ~ 1.13
通过编译器在函数调用时插入抢占检查指令,在函数调用时检查当前 Goroutine 是否发起了抢占请求,实现基于协作的抢占式调度;
Goroutine 可能会因为垃圾回收和循环长时间占用资源导致程序暂停;(因为一直执行某个函数逻辑,不调用其它函数,就没办法触发抢占调度)
基于信号的抢占式调度器 - 1.14 ~ 至今
实现基于信号的真抢占式调度;
垃圾回收在扫描栈时会触发抢占调度;
抢占的时间点不够多,还不能覆盖全部的边缘情况;
非均匀存储访问调度器 提案
对运行时的各种资源进行分区
GM模型
模型结构
G: Goroutine
M:Machine(与os线程一一对应)
缺点
创建,销毁跟调度G都需要每个M获取到全局队列的锁,竞争非常激烈
G在线程种传递问题(如一个G创建出一个新的G,新G需要放到全局队列中,被其它线程获取执行,增大了调度延迟)
Per-M的内存问题,每个M都有一个cache,然而实际上只有在运行的M才需要Cache,这就导致,很多阻塞在系统调用的M产生了内存浪费
GPM模型
模型结构
G: Goroutine
P: processor 处理器
数量
默认情况下就是你CPU核数个P(超线程下就是2倍),可配置
本地队列
存放等待运行的G
最多拥有256个G
M:Machine(与os线程一一对应)
对比GM改变
把mcache从M移动到P中
不再是都用全局的G队列了,每个P都有自己的G队列,新的G优先放入到自己的队列中,满了后在批量放到全局队列中,优先从自己的本地队列中获取G执行
实现了work stealling,当某个P的队列中没有可运行的G时,可以从全局队列,或者其它P中获取G执行
当因为网络或者锁进行切换时,G与M分离,M通过调度执行新的G(异步阻塞)
当因为系统调用阻塞或cgo运行一段时间后,sysmon携程会将P与M分离,由其它的M与P绑定,执行P中的G
调度策略
复用线程
避免频繁的创建跟销毁线程,而是复用线程
hand off机制
当前的正在执行的G进行系统调用阻塞线程时,让P跟M解绑,把P转移给其它空闲的线程执行
work-stealing
当某个P的队列中没有可运行的G时,可以从全局队列,或者其它P中获取G执行
利用并行
最多有GOMAPXPROCS个线程分布在多个CPU上运行
抢占
通过抢占式调度,一个Goroutine最多运行10ms,防止其它Goroutine饿死
全局G队列
当M执行work-stealing从其它P偷不到G时
监控线程sysmon
执行间隔
初始时时20us,运行1ms后逐渐翻倍,最后时10ms运行一次
抢占成功后,恢复成20us
工作内容
打印调度信息:如果开启schdule trace的debug信息(例如GODEBUG=schedtrace=5000,scheddetail=1), 则按照给定的间隔打印调度信息
定时从 netpoll 中获取 ready 的协程: 每sysmon tick进行一次netpoll(在STW结束和M执行查找可运行的G时也会执行netpoll)获取fd事件, 将与之相关的G放入全局runqueue
调度等待执行的timer,把空闲的M和P来执行timer任务
如果GC两分钟都没有运行的话,强制执行GC
执行抢占
系统调用,P跟M进行脱离
非系统调用,通知抢占(执行时间大于10ms的G)
归还内存:每5分钟归还GC后不再使用的span给操作系统(scavenge)
引用
https://www.ardanlabs.com/blog/2018/08/scheduling-in-go-part1.html
https://zhao520a1a.github.io/2020/09/10/Go%E8%B0%83%E5%BA%A6%E7%AF%87/
https://learnku.com/articles/41728
https://www.zhihu.com/question/20862617
https://morsmachine.dk/go-scheduler
https://draveness.me/golang/docs/part3-runtime/ch06-concurrency/golang-goroutine/
0 条评论
下一页