基本概念

协程是基于线程(用户级线程)。内部实现上,维护了一组数据结构和 n 个线程。协程执行的代码被放到一个待执行队列中,由线程从队列拉出来执行。

  • G (Goroutine) ,代表协程,也就是每次代码中使用go 关键字 时创建的一个对象
  • M (Work Tread) 工作线程
  • P (Processor) 处理器

Goroutine 从入队到执行

  1. 当创建一个 Goroutine 的时候,会加入到本地队列或者全局队列。
  2. 如果还有空闲的处理器(P),则创建一个工作线程(M)绑定该 P,注意:
  • 先找到一个空闲的处理器(P),如果没有则直接返回。
  • P 不会超过 CPU 个数
  • P 在被 M 绑定之后,就会初始化自己的 G 队列,此时是一个空队列
  • 无论在哪个 M 创建了 G ,只要 P 有空闲,就会引起新的 M 创建
  • 不需要考虑当前所在 M 中所绑的 P 的 G 队列是否已满
  • 新创建的 M 所绑的 P 的初始化队列会从其他 G 队列中取任务过来
  • 这里有一个问题:
  • 如果一个 G 的任务执行时间太长,他就会一直占用 M 线程,其他的 G 就会阻塞,如何避免这个情况的出现?

  1. M 会启动一个底层线程循环执行能找到的 G 任务,这里的寻找 G 任务从下面几方面找:
  • 当前 M 所绑的 P 队列中找
  • 去别的 P 队列找
  • 去全局 G 队列找
  1. G 任务的执行顺序是,先从本地队列找,本地没有找全局
  2. 程序启动的时候,首先跑主线程,然后主线程会绑定第一个 P
  3. 入口函数 main ,其实是作为一个 G 来执行的

使用协程真的能够节省时间吗?

协程可以节省进程间的切换和锁的争用时间