可增长的栈
每个OS线程都有一个固定大小的栈内存(通常为2MB), 栈内存区域用于保存在其他函数调用期间那些正在执行或临时暂停的函数中的局部变量。这个固定的栈大小既太大又太小。对于一个小的goroutine,2MB的栈是一个巨大的浪费。
作为对比,一个goroutine在生命周期开始时只有一个很小的栈,典型情况下为2KB。与OS线程类似,goroutine的栈也用于存放那些正在执行或者临时暂停的函数中的局部变量。但与OS线程不同的是,goroutine的栈不是固定大小的,它可以按需增大和缩小。goroutine的栈大小限制可以达到1GB。
goroutine调度
OS线程由OS内核来调度。每个几毫秒,一个硬件时钟终端发到CPU,CPU调用一个叫调度器的内核函数。这个函数暂停当前正在运行的线程,把他的集群器信息保存到内存,查看线程列表并决定接下来运行哪一个线程,再从内存恢复线程的注册表信息,最后继续执行选中的线程。因OS线程由内核来调度,所以控制权限从一个线程到另外一个线程需要一个完整的上下文切换。考虑这个操作设计的内存局域性以及涉及的内存访问数量,还有访问内存所需的CPU周期数量的增加,这个操作其实是很慢的。
Go运行时包含一个自己的调度器,这个调度器使用一个称为m:n调度的技术(因为它可以复用/调度m个goroutine到n个OS线程)。Go调度器与内核调度器的工作类似,但Go调度器只需要关心单个Go程序的goroutine调度问题。
与操作系统的线程调度器不通的是,Go调度器不是由硬件时钟来定期触发的,而是由特定的Go语言结构来触发的。比如当一个goroutine调用time.Sleep或被通道阻塞或对互斥量操作时,调度器就会将这个goroutine设为休眠模式,并运行其他goroutine直到前一个可重新唤醒为止。因为它不需要切换到内核语境,所以调用一个goroutine比调度一个线程成本低很多。
GOMAXPROCS
Go调度器使用GOMAXPROCS参数来确定需要使用多少个OS线程来同时执行Go代码。默认值是机器上的CPU数量,所以在一个有8个CPU的机器上,调度器会把Go代码同时调度到8个OS线程上。(GOMAXPROCS是m:n调度中的n。)正在休眠或者正被通道通信阻塞的goroutine不需要占用线程。阻塞在I/O和其他系统调用中或调用非Go语言写的函数的goroutine需要一个独立的OS线程,但这个线程不计算在GOMAXPROCS内。
可以用GOMAXPROCS环境变量或者runtime.GOMAXPROCS函数来显示控制这个参数
goroutine没有标识
在大部分支持多线程的操作系统和编程语言里,当前线程都有一个独特的标识,它通常可以取一个整数或者指针。这个特性让我们可以轻松构建一个线程的局部存储,它本质上就是一个全局的map,以线程的标识作为键,这样每个线程都可以独立的用这个map存储和获取值,而不受其他线程干扰
goroutine没有可供程序员访问的标识。Go语言鼓励一种更简单的编程风格,其中,能影响一个函数行为的参数应当是显示指定的。这不仅让程序更易阅读,还让我们能自由的把一个函数的子任务分发到多个不同的goroutine而无需担心这些goroutine的标识