协程是一种用户态的轻量级线程,又称微线程,协程的调度完全由用户控制。与传统的系统级线程和进程相比,协程的最大优势在于其”轻量级”,可以轻松创建上百万个而不会导致系统资源衰竭,而线程和进程通常最多也不能超过1万的。这也是协程也叫轻量级线程的原因。

1.1 Goroutine

创建Goroutine的成本很小,它就是一段代码,一个函数入口。以及在堆上为其分配的一个堆栈(初始大小为4K,会随着程序的执行自动增长删除)。因此它非常廉价,Go应用程序可以并发运行数千个Goroutines。

  1. 与线程相比,Goroutines非常便宜。它们只是堆栈大小的几个kb,堆栈可以根据应用程序的需要增长和收缩,而在线程的情况下,堆栈大小必须指定并且是固定的
  2. Goroutines被多路复用到较少的OS线程。在一个程序中可能只有一个线程与数千个Goroutines。如果线程中的任何Goroutine都表示等待用户输入,则会创建另一个OS线程,剩下的Goroutines被转移到新的OS线程。所有这些都由运行时进行处理,我们作为程序员从这些复杂的细节中抽象出来,并得到了一个与并发工作相关的干净的API。
  3. 当使用Goroutines访问共享内存时,通过设计的通道可以防止竞态条件发生。通道可以被认为是Goroutines通信的管道。

1.2 主goroutine

封装main函数的goroutine称为主goroutine。

主goroutine的任务:

  1. 创建一个defer语句,用于在主goroutine退出时做必要的善后工作。
  2. 启动垃圾回收的gotoutine。
  3. 执行main包中的init函数
  4. 执行main函数。

1.3 goroutine与线程

可增长的栈

线程都具有一个固定大小的栈内存(2MB),作为对比,一个goroutine在生命周期开始时只有一个很小的栈,典型情况为2KB。goroutine的栈不是固定大小的,它可以按需增大或者缩小,goroutine栈最大可达1GB。

goroutine调度

OS线程由内核调度器函数来调度,过程为:保存一个线程的状态到内存,再恢复另外一个线程的状态,最后更新调度器的数据结构。这个过程较为缓慢。

Go有自己的调度器,采用m:n调度技术(复用/调度m个goroutine到n个os线程)。Go调度器和内核调度器的工作类似,但Go调度器只需关心单个Go程序的goroutine调度问题。

os线程调度器由硬件时钟定期触发,Go调度器并非如此。它不需要切换到内核环境,所以调用一个goroutine比调度一个线程的成本低很多。