go语言实现阻塞队列 go 阻塞队列( 四 )


系统调用阻塞
当M执行某一个G时候如果发生了阻塞操作 , M会阻塞,如果当前有一些G在执行,调度器会把这个线程M从P中摘除 , 然后再创建一个新的操作系统的线程(如果有空闲的线程可用就复用空闲线程)来服务于这个P 。当M系统调用结束时候,这个G会尝试获取一个空闲的P执行,并放入到这个P的本地队列 。如果获取不到P,那么这个线程M变成休眠状态,加入到空闲线程中 , 然后这个G会被放入全局队列中 。
队列轮转
可见每个P维护着一个包含G的队列,不考虑G进入系统调用或IO操作的情况下,P周期性的将G调度到M中执行 , 执行一小段时间 , 将上下文保存下来,然后将G放到队列尾部 , 然后从队列中重新取出一个G进行调度 。
除了每个P维护的G队列以外 , 还有一个全局的队列,每个P会周期性地查看全局队列中是否有G待运行并将其调度到M中执行,全局队列中G的来源,主要有从系统调用中恢复的G 。之所以P会周期性地查看全局队列,也是为了防止全局队列中的G被饿死 。
除了每个P维护的G队列以外,还有一个全局的队列,每个P会周期性地查看全局队列中是否有G待运行并将其调度到M中执行,全局队列中G的来源,主要有从系统调用中恢复的G 。之所以P会周期性地查看全局队列,也是为了防止全局队列中的G被饿死 。
M0
M0是启动程序后的编号为0的主线程 , 这个M对应的实例会在全局变量rutime.m0中,不需要在heap上分配,M0负责执行初始化操作和启动第一个G , 在之后M0就和其他的M一样了
G0
G0是每次启动一个M都会第一个创建的goroutine,G0仅用于负责调度G,G0不指向任何可执行的函数,每个M都会有一个自己的G0 , 在调度或系统调用时会使用G0的栈空间,全局变量的G0是M0的G0
一个G由于调度被中断,此后如何恢复?
中断的时候将寄存器里的栈信息,保存到自己的G对象里面 。当再次轮到自己执行时,将自己保存的栈信息复制到寄存器里面,这样就接着上次之后运行了 。
我这里只是根据自己的理解进行了简单的介绍,想要详细了解有关GMP的底层原理可以去看Go调度器 G-P-M 模型的设计者的文档或直接看源码
参考:()
()
go语言循环队列的实现队列的概念在 顺序队列 中,而使用循环队列的目的主要是规避假溢出造成的空间浪费,在使用循环队列处理假溢出时,主要有三种解决方案
本文提供后两种解决方案 。
顺序队和循环队列是一种特殊的线性表,与顺序栈类似 , 都是使用一组地址连续的存储单元依次存放自队头到队尾的数据元素,同时附设队头(front)和队尾(rear)两个指针,但go语言实现阻塞队列我们要明白一点 , 这个指针并不是指针变量,而是用来表示数组当中元素下标的位置 。
本文使用切片来完成的循环队列,由于一开始使用三个参数的make关键字创建切片,在输出的结果中不包含nil值(看起来很舒服),而且在验证的过程中发现使用append()函数时切片内置的cap会发生变化,在消除go语言实现阻塞队列了种种障碍后得到go语言实现阻塞队列了一个四不像的循环队列,即设置的指针是顺序队列的指针,但实际上进行的操作是顺序队列的操作 。最后是对make()函数和append()函数的一些使用体验和小结,队列的应用放在链队好了 。
官方描述(片段)
即切片是一个抽象层,底层是对数组的引用 。
当我们使用
构建出来的切片的每个位置的值都被赋为interface类型的初始值nil,但是nil值也是有大小的 。

推荐阅读