| 阅读 | 共 895 字,阅读约
什么是协程
软件运行时,计算机真正干活的是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的资源