goruntine

基本概念
进程:独立的栈空间,独立的堆空间,进程之间调度由os完成。

线程:独立的栈空间,共享堆空间,内核线程之间调度由os完成。

协程:独立的栈空间,共享堆空间,调度由用户自己控制,本质上有点类似于用户级线程,这些用户级线程的调度也是自己实现的。

同步调用 就是由调用者主动等待这个调用的结果。发出一个调用,在没有得到结果之前,该调用就不返回。一旦调用返回,就得到返回值了。

异步调用 调用在发出之后,这个调用结果就直接返回了。当一个异步调用过程在发出之后,调用者不会立即得到结果,而是在调用发出后,被调用者通过状态、通知来通知调用者,或者通过函数回调来处理这个调用。

阻塞与非阻塞 关注的是:程序在等待调用结果(消息 返回值)时的状态

阻塞调用 是指调用结果返回之前,当前线程会被挂起。调用线程只有在得到结果之后才会返回。

非阻塞调用 是指,在不能立刻得到结果之前,该调用不会阻塞当前线程,当前线程还会继续执行下去。

注意!!! 阻塞与非阻塞与是否同步和是否异步无关。

可以说,协程与线程主要区别是它将不再被内核调度,而是交给了程序自己而线程是将自己交给内核调度,所以也不难理解golang中调度器的存在。

语言级别实现了并发,我们为什么会这样说呢?愿意就在于此,Go有自己的scheduler。

如果进程切换过于频繁,系统资源就会被频繁开销所占去。于是控制粒度再进一步细化,即把“拥有资源”和“独立调度”两个属性分开来。线程仅仅拥有很小的一部分资源,共享线程的资源。其开销显著地小于进程的切换,操作系统的书里,都说的很细致,就不再赘述。

把调度的那部分功能从内核中拿出来,在进程中自己去实现一个逻辑流调度的功能,这样既可以实现并发的优势,又可以避免反复的系统调用,减少线程切换造成的开销,这就叫做用户态线程,相当于是调度功能的更细粒度的实现。

所以Go就需要自己单独的开发一个自己使用的调度器,能够自己管理goruntines,

协程的思想本质上就是控制流的主动让出(yield)和恢复(resume)机制”的理解

协程之间的切换是由程序自身控制的,没有线程切换的额外开销,和多线程相比,线程数目越多,协程的性能优势就越明显。

通常情况下有三种线程模型:

N:1 N个用户级线程以及一个内核级线程。这种模式下,用户级线程之间的切换可以很快,但是不能很好的利用多核的优势。
1:1 一个用户级线程对应一个内核级线程。可以利用多核的优势,但是线程切换比较慢,因为需系统调用,要进行trap操作。
Golang中的调度器采用 M:N 的方式。既要利用多核cpu系统的特性,同时还要增强上下文切换的速度。缺点就是,这会使得调度器的实现变得复杂。

每一个Go程序都附带一个runtime,runtime负责与底层操作系统交互,也都会有scheduler对goruntines进行调度。在scheduler中有三个非常重要的概念:P,M,G。

地鼠(gopher)用小车运着一堆待加工的砖。M就可以看作图中的地鼠,P就是小车,G就是小车里装的砖。

图中看,有2个物理线程M,每一个M都拥有一个context(P),每一个也都有一个正在运行的goroutine。
P的数量可以通过GOMAXPROCS()来设置,它其实也就代表了真正的并发度,即有多少个goroutine可以同时运行。
图中灰色的那些goroutine并没有运行,而是出于ready的就绪态,正在等待被调度。
P维护着这个队列(称之为runqueue),Go语言里,启动一个goroutine很容易:go function 就行,所以每有一个go语句被执行,runqueue队列就在其末尾加入一个goroutine,在下一个调度点,就从runqueue中取出(如何决定取哪个goroutine?)一个goroutine执行。

threads粒度太粗,会有诸多额外开销 -> goroutine不需要这些开销,它们需要更细粒度的控制 -> golang中scheduler的模型(几种线程模型 M P G 含义 )-> (M P G 优点) 某个G陷入阻塞的时候 P可以带着其他的G转移到其他的M上,当原先的G系统调用完成以后,会从另外一个地方steal一个P回来 -> 提高资源利用率。

你可能感兴趣的:(go)