38

go concurrency

 4 years ago
source link: https://www.tuicool.com/articles/ueMZZf7
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.
neoserver,ios ssh client
Do not communicate by sharing memory; instead, share memory by communicating

GMP

G

  • G即 Goroutine, 它包括堆栈、指令指针和其他对调度goroutines很重要的信息
  • go不同版本Goroutine默认栈大小不同
  • 线程是运行Goroutine的实体, 调度器的功能是把可以运行的Goroutine分配到工作线程上

M

  • M即工作线程,所有M都是有线程栈的, 每个M代表了1个内核线程.OS调度器负责把内核线程分配到CPU的核上运行
  • M必须和P关联才能运行G
  • work stealing: 当M绑定的P没有可运行的G时,它可以从其他运行的M那里偷取G
  • 线程想要运行任务需要获取P, 从P的本地队列获取G,
  • P本地队列为空时, M也会尝试从全局队列获取一批G放到本地P的本地队列
  • 或从其他P的本地队列偷一般放到自己P的本地队列
  • M运行G, G执行之后, M会从P获取下一个G, 不断重复下去

P

  • P即Processor是一个抽象的概念, 并不是真正的物理CPU, 它包含了运行Goroutine的资源
  • 线程想运行G, 必须先获取P, P中包含了可运行的G队列
  • P需要和M进行绑定, 构成一个执行单元
  • P决定了同时可以并发任务的数量

通过runtime.GOMAXPROCS限制同时执行用户级任务的操作系统线程, Go1.5以后被设置可用的核数

  • P有两种队列
    1. 本地队列

    当前P的队列,本地队列是Lock-Free,没有数据竞争问题,无需加锁处理,可以提升处理速度。

    存放的也是等待运行的G,存的数量有限

    1. 全局队列

    全局队列为了保证多个P之间任务的平衡。所有M共享P全局队列,

    为保证数据竞争问题,需要加锁处理。相比本地队列,处理速度要低

GMP调度过程

YBj2Qfv.png!web

gpm.png

  1. 正常情况下, 每个P(context)都会有local runqueue, 挂载若干的G, 其中只有一个G正在M上运行

  2. 每个P除了local runqueue, 所有的P还共享一个全局的global runqueue, 在某些情况下会将G挂载到这个global runqueue下,

  3. 当一个G系统阻塞了, 比如(调用了sleep或者I/O系统调用), G处于_Gsyscall状态,M也处于 block on syscall 状态,此时的M可被抢占调度,

    由于线程不能同时执行代码和被阻塞在syscall上,所以我们需要传递上下文,以便它能够继续调度

    go runtime却不会让对应的P(context)被阻塞(想一下,P的local runqueue下此时可能还挂了一串而goroutine呢) 而是去线程缓存池(M cached pool)中Get一个M出来如下图M1,把自己挂上去,从local runqueue上pop一个G出来继续执行;如果没有其它idle的M,但P的Local队列中仍然有G需要执行,则创建一个新的线程,并将P与之绑定,顺序执行P中下一个G.

    JbaMBj3.jpg!web

    syscall.jpg

  4. 上一步中,P带着自己的local runqueue 跑路了,扔下M被阻塞,但是当时触发阻塞的G却并不会跟着P一起跑路,(G是一个比较厚道的人);

  5. M终于从blocked(e.g. I/O syscall)中返回了,此时想要继续执行这个G,但那是不可能的,因为G必须在P这个context下执行,所以这个M就去找空闲的P了,如果找到了,就把这个G挂上去开始执行,如果找不到,就把G加入到global runqueue(3中提到过,记得么?)中,而自己则很乖地去线程池报道(4中会用到);

  6. 为了避免global runqueue出现工地悲剧,每个P会时不时地去看下global runqueue是不是有G,避免这些G被饿死;(恩,大家都很nice呢);

  7. 当某个M-P下已经没有G的时候,P就会难掩助人为乐的精神,随机地跑到其它P下偷一半的G过来自己执行( 如下图)

  8. 当通过go关键字创建一个新的goroutine的时候,它会优先被放入P的本地队列。

    fEVf2az.jpg!web

    in-motion.jpg

Stealing work

Bvm2aaY.jpg!web

steal.jpg

参考文献:


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK