每个OS的线程都有一个固定大小的栈内存,通常是2MB,栈内存区域用于保存在其它函数调用期间那些正在执行或临时暂停的函数中的局部变量,这样固定大小的栈,既太大,又太小,往往不能满足多变的场景。 2MB固定大小的栈,对于执行简单操作的goroutine来说,是一种巨大的浪费,比如有的goroutine仅仅等待一个waitgroup在关闭一个通道; 在go程序中,一次创建十万左右的goroutine也不罕见,对于这种情况栈就太大了2KB*100,000=200MB,但对于最复杂和深度递归的函数,固定大小的栈始终不够大。改变这种固定大小可以提高空间效率并允许创建更多的线程,或者也可以容许更深的递归函数,但无法做到同时满足两点。 对goroutine在生命周期开始时只有一个很小的栈,典型情况是2KB,与OS线程类似,goroutine的栈也用于存放那些正在执行或临时暂停的函数的局部变量。但与OS线程不同的是,goroutine的栈不是固定大小的,他可以按需增大和缩小。大到可以达到1GB,比线程典型的固定大小栈高几个数量级。当然只有极少的goroutine会使用这么大的栈 \
OS的线程由OS内核调度,每隔几毫秒,硬件就发送中断到CPU,CPU调用调度器内核函数,进而引发当前线程的暂停和下个线程的运行,而线程与线程之间的切换,需要一个完整的上下文切换。由于引发内存访问数量的增加和CPU等待周期的增加,这一操作是非常耗时的。 但是Go运行的时候包含一个自己的调度器,这个调度器使用一个称为一个M:N调度技术,把m个goroutine调度到n个os线程上运行,我们可以用GOMAXPROCS来控制n的数量,Go的调度器不是由硬件时钟来定期触发的,而是由特定的go语言结构来触发的,全部在用户态实现,不需要切换到内核语境,因此调度一个goroutine比调度一个线程的成本低很多