1、goroutine中栈的大小是动态变化的
每个os线程都有一个固定大小的栈内存(通常为2MB),栈内存用于保存在其他函数调用期间那些正在执行或临时暂停的函数中的局部变量。而goroutine在生命周期开始时只有一个很小的栈(通常为2KB),goroutine的栈不是固定大小的,它可以按需增大和缩小。所以goroutine能够更高效的使用栈空间。
2、goroutine调度
os线程是由操作系统内核调度的,所以控制权限从一个线程到另一个线程需要一次完整的上下文切换:即保存一个线程的状态到内存,再恢复另一个线程的状态,最后更新调度器的数据结构。考虑到内存局部性和内存的访问次数,还有访问内存所需的cpu周期数量的增加,这个操作是比较耗时的。go运行时包含一个自己的调度器,采用m:n调度的技术(复用/调度m个goroutine到n个os线程)。go调度器是由特定的go语言结构触发的,比如一个goroutine调用time.Sleep或被通道阻塞或对互斥量操作时,调度器会将这个goroutine设置为休眠模式,并运行其它goroutine。因为它不需要切换到内核中,所以调用一个goroutine比调度一个线程成本低很多。
3、GOMAXPROCS
go调度器使用GOMAXPROCS参数来确定需要使用多少个os线程来同时执行go代码。默认是机器上的cpu数量。正在休眠或者正被通道通信阻塞的goroutine不需要占用线程。阻塞在I/O和其它系统调用中或调用非go语言写的函数的goroutine需要一个独立的OS线程,这个线程不计算在GOMAXPROCS中。
4、goroutine没有标识
在大部分支持多线程的操作系统和编程语言里,当前线程都有一个独特的标识,它通常可以获取一个整数或者指针。这个特性可以轻松构建一个线程的局部存储,以线程标识为键值的map,这样每个线程都可以独立的使用这个map存储和获取值。goroutine没有提供可供程序员访问的标识,这是由设计决定的,因为线程局部存储有一种被滥用的倾向,导致函数的行为不仅取决于它的参数,还取决于运行它的线程标识。因此,在线程的标识需要改变的场景,这些函数的行为就会变的诡异莫测。
有疑问加站长微信联系(非本文作者)