台式机&硬件面试官:换人!他连进程线程协程这几个特点都说不出( 四 )


如果用多进程 , 不仅存在频繁调度切换问题 , 同时还会存在每个进程资源不共享的问题 , 需要额外引入进程间通信机制来解决 。
协程出现给高并发和 IO 密集型服务开发提供了另一种选择 。
当然 , 世界上没有技术银弹 。 在这里我想把协程这把钥匙交到你手中 , 但是它也不是万能钥匙 , 最好的解决方案是贴合自身业务类型做出最优选择 , 不一定就选择一种模型 , 有时候是几种模型的组合 , 比如多线程搭配协程是常见的组合 。
什么是协程
那什么是协程呢?协程 Coroutines 是一种比线程更加轻量级的微线程 。 类比一个进程可以拥有多个线程 , 一个线程也可以拥有多个协程 , 因此协程又称微线程和纤程 。
台式机&硬件面试官:换人!他连进程线程协程这几个特点都说不出
本文插图

协程图解
可以粗略的把协程理解成子程序调用 , 每个子程序都可以在一个单独的协程内执行 。
台式机&硬件面试官:换人!他连进程线程协程这几个特点都说不出
本文插图
协程子程序模型 调度开销
线程是被内核所调度 , 线程被调度切换到另一个线程上下文的时候 , 需要保存一个用户线程的状态到内存 , 恢复另一个线程状态到寄存器 , 然后更新调度器的数据结构 , 这几步操作设计用户态到内核态转换 , 开销比较多 。
台式机&硬件面试官:换人!他连进程线程协程这几个特点都说不出
本文插图
线程切换
协程的调度完全由用户控制 , 协程拥有自己的寄存器上下文和栈 , 协程调度切换时 , 将寄存器上下文和栈保存到其他地方 , 在切回来的时候 , 恢复先前保存的寄存器上下文和栈 , 直接操作用户空间栈 , 完全没有内核切换的开销 。
台式机&硬件面试官:换人!他连进程线程协程这几个特点都说不出
本文插图
协程切换 动态协程栈

协程拥有自己的寄存器上下文和栈 , 协程调度切换时将寄存器上下文和栈保存下来 , 在切回来的时候 , 恢复先前保存的寄存器的上下文和栈 。
Goroutine 是 Golang 的协程实现 。 Goroutine 的栈只有 2KB大小 , 而且是动态伸缩的 , 可以按需调整大小 , 最大可达 1G 相比线程来说既不浪费又灵活了很多 , 可以说是相当的nice了!
线程也都有一个固定大小的内存块来做栈 , 一般会是 2MB 大小 , 线程栈会用来存储线程上下文信息 。 2MB 的线程栈和协程栈相比大了很多 。
台式机&硬件面试官:换人!他连进程线程协程这几个特点都说不出
本文插图
线程和协程栈对比 协程实现 Python协程实现
python 2.5 中引入 yield/send 表达式用于实现协程 , 但这种通过生成器的方式使用协程不够优雅 。
python 3.5 之后引入async/await, 简化了协程的使用并且更加便于理解 。
Go语言协程实现
Golang 在语言层面实现了对协程的支持 , Goroutine 是协程在 Go 语言中的实现 ,在 Go 语言中每一个并发的执行单元叫作一个 Goroutine, Go 程序可以轻松创建成百上千个协程并发执行 。
Go 协程调度器有三个重要数据结构:
G 表示 Goroutine, 它是一个待执行的任务;
M 表示操作系统的线程 , 它由操作系统的调度器调度和管理;
P 表示处理器 Processor , 它可以被看做运行在线程上的本地调度器;


推荐阅读