goroutine与操作系统(OS)线程的区别

1、可增长的栈

每个OS线程都有一个固定大小的栈内存(通常为2MB),栈内存区域用于保护在其它函数调用期间那些正在执行或临时暂停的函数中的局部变量。

而一个goroutine在生命周期开始时只用一个很小的栈,典型情况下为2KB,但它的栈大小不是固定不变的,可以按需增大和缩小,大小限制可以达到1GB。

2、goroutine调度

OS线程又OS内核来调度。每个几毫秒,一个硬件时钟中断发送到CPU,CPU调用一个叫调度器的内核函数。这个函数暂停当前正在执行的线程,把它的寄存器信息保存到内存,查看线程列表决定接下来运行哪一个线程,再从内存恢复线程的注册表信息,最后继续执行选中的线程。因为OS线程由内核来调度,所以控制权限从一个线程到另外一个线程需要一个完整的上下文切换(context switch):即保存一个线程的状态到内存,在回复另一个线程的状态,最后更新调度器的数据结构。因为内存的局限性以及涉及的内存访问数量,当有访问内存所需的CPU周期数的增加,这个操作其实是很慢的。

GO运行时包含一个自己的调度器,使用m:n调度的技术(复用/调度m个goroutine到n个OS线程)。GO调度器不是由硬件时钟来触发,而是由特定的GO语言结构来触发,如通道被阻塞或对互斥量进行操作时,调度器会将这个goroutine设置为休眠模式,并运行其他的goroutine直到前一个可重新唤醒为止。因为不需要切换到内核环境,所以调用一个goroutine比调度一个线程成本低很多。

3、GOMAXPROCS

GO调度器使用GOMAXPROCS参数来确定需要使用多少个OS线程来同时执行GO代码。默认值是机器上CPU的数量。所以在一个8C的机器上,调度器会把GO代码同时调度到8个OS线程上(ps:并行?),正在休眠或被通道通信阻塞的goroutine不需要占用线程。(ps:阻塞在I/O和其它系统调用中,或者是调用非GO语言写的函数的goroutine需要一个独立的OS线程,但是不计算在GOMAXPROCS内)

4、goroutine没有标识

在大部分支持多线程的操作系统或语言中,当前线程都会有一个标识,通常是一个整数或指针。但在GO没有线程标识,因为GO鼓励更简单的编程风格,认为能影响一个函数行为的参数应当是显示指定的。 


书山有路勤为径 学海无涯苦作舟