进程、线程、协程、goroutine

进程:独立分配系统资源,不与其他进程共享堆栈

线程:线程是进程的一个实体(执行体),是CPU调度和分派的基本单位,它是比进程更小的能独立运行的基本单位.线程自己基本上不拥有系统资源,只拥有一点在运行中必不可少的资源(如程序计数器,一组寄存器和栈),但是它可与同属一个进程的其他的线程共享进程所拥有的全部资源。同一个进程下的线程共享堆,不共享栈

协程:和线程类似,进程线程的切换主要依赖于时间片的轮转,而协程切换主要依赖于自身。

goroutine: go语言实现的协程,并且是go原生支持的(通过runtime实现),协程算是线程下的,跟线程和进程的关系一样,这是我个人的理解,要是错了不负责啊。每个协程初始时堆栈4k,会随着运行变大和回收。

Goroutine的实现原理

  • 两种备选方案 
    • (M:1)多个用户态的线程对应一个系统线程,它可以做快速的上下文切换。缺点是不能有效利用多核CPU
    • (1:1)一个用户态的线程对应一个系统线程,它可以利用多核机制,但上下文切换需要消耗额外的资源
  • Golang的做法

    • (M:N)Golang采取了一种多对多的方案。M个用户线程对应N个系统线程,缺点增加了调度器的实现难度

进程、线程、协程、goroutine_第1张图片

那么go中切换goroutine的调度点有哪些呢?具体有以下三种情况

  • 调用runtime·gosched函数。goroutine主动放弃CPU,该goroutine会被设置为runnable状态,然后放入一个全局等待队列中,而P将继续执行下一个goroutine。使用runtime·gosched函数是一个主动的行为,一般是在执行长任务时又想其它goroutine得到执行的机会时调用。
  • 调用runtime·park函数。goroutine进入waitting状态,除非对其调用runtime·ready函数,否则该goroutine将永远不会得到执行。而P将继续执行下一个goroutine。使用runtime·park函数一般是在某个条件如果得不到满足就不能继续运行下去时调用,当条件满足后需要使用runtime·ready以唤醒它(这里唤醒之后是否会加入全局等待队列还有待研究)。像channel操作,定时器中,网络poll等都有可能park goroutine。
  • 慢系统调用。这样的系统调用会阻塞等待,为了使该P上挂着的其它G也能得到执行的机会,需要将这些goroutine转到另一个OS线程上去。具体的做法是:首先将该P设置为syscall状态,然后该线程进入系统调用阻塞等待。之前提到过的sysmom线程会定期扫描所有的P,发现一个P处于了syscall的状态,就将M和P分离(实际上只有当 Syscall 执行时间超出某个阈值时,才会将 M 与 P 分离)。RUNTIME会再分配一个M和这个P绑定,从而继续执行队列中的其它G。而当之前阻塞的M从系统调用中返回后,会将该goroutine放入全局等待队列中,自己则sleep去。 

那就会有个问题,如果一个系统调用或者G任务执行太长,他就会一直占用这个线程,由于本地队列的G任务是顺序执行的,其它G任务就会阻塞了,怎样中止长任务的呢?(这个地方我找了好久~o()o

这样滴,启动的时候,会专门创建一个线程sysmon,用来监控和管理,在内部是一个循环:

1.     记录所有PG务计schedtick,(schedtick会在每行一个G增)

2.     如果检查 schedtick一直没有增,P一直在行同一个G,如果超一定的时间10ms),就在G信息里面加一个标记

3.     然后G行的候,如果遇到非内函数用,就会检查一次标记,然后中断自己,把自己加到列末尾,行下一个G


调度点的情况说清楚了,但整个模型还并不完整。我们知道当使用go去调用一个函数,会生成一个新的goroutine放入当前P的队列中,那么什么时候生成别的OS线程,各个OS线程又是如何做负载均衡的呢?

当M从队列中拿到一个可执行的G后,首先会去检查一下,自己的队列中是否还有等待的G,如果还有等待的G,并且也还有空闲的P,此时就会通知runtime分配一个新的M(如果有在睡觉的OS线程,则直接唤醒它,没有的话则生成一个新的OS线程)来分担任务。

如果某个M发现队列为空之后,会首先从全局队列中取一个G来处理。如果全局队列也空了,则会随机从别的P那里直接截取一半的队列过来(偷窃任务),如果发现所有的P都没有可供偷窃的G了,该M就会陷入沉睡。

整个调度模型大致就是这样子了,和所有协程的调度一样,在响应时间上,这种协作式调度是硬伤。很容易导致某个协程长时间无法得到执行。但总体来说,它带来的好处更加让人惊叹。想要了解的更多可以看看我下面列出的一些参考资料,或是直接看它的源码:http://golang.org/src/runtime/proc.c


参考链接: https://blog.csdn.net/justaipanda/article/details/44064811

你可能感兴趣的:(Golang,runtime,协程,线程)