如果我正确理解goroutines如何在系统线程之上工作 - 它们逐个从队列中运行。但这是否意味着每个goroutine加载\卸载CPU的上下文?如果是,系统线程和goroutines之间有什么区别?
最重要的问题是上下文切换的时间成本。这是对的吗?
在检测哪个数据被哪个goroutine请求时,有什么机制?例如:我从goroutine A向DB发送请求,并且不等待响应,同时发生切换到下一个goroutine。系统如何理解请求来自A而不是来自B或C?
答案 0 :(得分:5)
Go有一个分段堆栈,可以根据需要增长。 Go运行时执行调度,而不是OS。运行时将goroutine复用到相对较少数量的实际OS线程上。
Goroutines是协同安排的,当发生切换时,只需要保存/恢复3个寄存器 - 程序计数器,堆栈指针和DX。从操作系统的角度来看,Go程序就像事件驱动程序一样。
您无法直接控制运行时将创建的线程数。通过调用runtime.GOMAXPROCS(n)
来设置变量GOMAXPROCS,可以设置程序使用的处理器核心数。
和完全不同的故事
在计算中,程序是计算机执行的一组特定的有序操作。指令是程序给计算机处理器的命令。在计算机内,地址是存储器或存储器中的特定位置。程序计数器寄存器是处理器使用的一小组数据保存位置之一。
这是不同的故事,程序如何相互作用和沟通,而且与goroutines主题没有直接关系。
来源:
答案 1 :(得分:2)
“ G”只是一个goroutine。用类型g表示。当goroutine退出时,其g对象将返回到空闲gs池中,以后可以重用于其他goroutine。
“ M”是一个OS线程,可以执行用户Go代码,运行时代码,系统调用或处于空闲状态。用类型m表示。一次可以有任意多个M,因为在系统调用中可能会阻塞任何数量的线程。
最后,“ P”表示执行用户Go代码所需的资源,例如调度程序和内存分配器状态。用类型p表示。确实有GOMAXPROCSP。可以将P视为OS调度程序中的CPU,而将p类型的内容视为每个CPU状态。这是放置状态的好地方,该状态需要进行分片以提高效率,但不必按线程或按规范进行。
调度程序的工作是匹配G(要执行的代码),M(要执行的位置)和P(要执行的权限和资源)。当M停止执行用户Go代码时(例如通过输入系统调用),它将M返回到空闲的P池。为了恢复执行用户Go代码(例如在系统调用返回时),它必须从空闲池中获取P。
所有g,m和p对象都是堆分配的,但是从不释放,因此它们的内存保持类型稳定。因此,运行时可以避免调度程序深度的写障碍。
每个非死G都有一个与之相关的用户堆栈,这是用户Go代码执行的对象。用户堆栈开始时很小(例如2K),并且会动态增长或缩小。
每个M都有一个与其关联的系统堆栈(也称为M的“ g0”堆栈,因为它实现为存根G),在Unix平台上,还有一个信号堆栈(也称为M的“ gsignal”堆栈) 。系统和信号堆栈无法增长,但足够大以执行运行时和cgo代码(纯Go二进制文件中为8K; cgo二进制文件中系统分配)。
运行时代码通常使用systemstack,mcall或asmcgocall临时切换到系统堆栈,以执行不能抢占,不能增长用户堆栈或切换用户goroutine的任务。在系统堆栈上运行的代码是隐式不可抢占的,并且垃圾收集器不会扫描系统堆栈。在系统堆栈上运行时,当前用户堆栈不用于执行。