| 阅读 |,阅读约 2 分钟
| 复制链接:

什么是协程

软件运行时,计算机真正干活的是CPU

单进程时代

程序串行进行,只有当一个程序运行完才能运行下一个进程

缺点:

  • 进程阻塞
  • cpu利用率低

多进程时代

进程按照时间片执行

优点:

  • 解决了阻塞问题,从宏观来看似乎多个进程同时执行

缺点:

  • 进程占用太多资源
  • 进程过多,调度时间占用过多cpu

协程时代

线程可以分为用户态线程和内核态线程,但是cpu并不知道用户态线程的存在,它只知道运行的是内核态线程。用户态线程运行必须和一个内核态线程绑定。

再细化分类一下:内核态线程依然叫做“线程”(thread),而用户态线程就称为“协程”(coroutine)

线程和协程的对比

线程 协程
cpu调度 用户态调度
抢占式 协作式

协程和线程的绑定关系对比

绑定关系 优点 缺点
1:1 最容易实现 协程的创建、删除、切换都由cpu完成,代价高昂
:1 协程在用户态完成切换,不会在内核态,切换非常轻量 一个进程的所有协程都绑定在一个线程,一旦某个协程阻塞,其他协程无法执行,没有并发能力
M:N 克服了前面的两个缺点 实现复杂

Go语言的协程

go中的协程成为goroutine,并通过go提供给的runtime进行调度。goroutine有如下特点:

  • 占用内存小,只有几kb
  • 调度灵活(runtime实现)
  • 程序员看不到底层细节,并发编程难度低

回顾历史:旧调度器有什么问题

Go 目前使用的调度器是 2012 年重新设计的,因为之前的调度器性能存在问题

旧调度器实现原理

  • 只有G和M,没有P。G是goroutine,M是thread
  • 维护全局协程队列
  • M执行、访问G都要访问全局队列,都要加锁保证并发

旧调度器的问题

  • 创建、销毁、调度 G 都需要每个 M 获取锁,这就形成了激烈的锁竞争
  • M 转移 G 会造成延迟和额外的系统负载。比如当 G 中包含创建新协程的时候,M 创建了 G’,为了继续执行 G,需要把 G’交给 M’执行,也造成了很差的局部性,因为 G’和 G 是相关的,最好放在 M 上执行,而不是其他 M’。
  • 系统调用 (CPU 在 M 之间的切换) 导致频繁的线程阻塞和取消阻塞操作增加了系统开销

调度器模型

GMP:GM的基础上新增的P,P包含了运行goroutine的资源

GMP模型

线程(M)是运行goroutine(G)的实体,P包含运行G的资源

参考