type
status
date
slug
summary
tags
category
icon
password
如何理解协程
协程相比之与进程、线程,更像是用户自己实现的库函数,是在操作系统之上的操作,保存堆栈上下文信息。
二.进程、线程、协程三者的上下文切换比较
ㅤ | 进程 | 线程 | 协程 |
切换者 | 操作系统 | 操作系统 | 用户(编程者/应用程序 |
切换时机 | 根据操作系统自己的切换策略,用户不感知 | 根据操作系统自己的切换策略,用户不感知 | 用户自己的程序决定 |
切换内容 | 页全局目录,内核栈,硬件上下文 | 内核栈,硬件上下文 | 硬件上下文 |
切换内容的保存 | 保存于内核栈中 | 保存于内核栈中 | 保存在用户自己的变量(用户栈或堆) |
切换过程 | 用户态 - 内核态 - 用户态 | 用户态 - 内核态 - 用户态 | 用户态(没用进入内核态) |
切换效率 | 低 | 中 | 高 |
ㅤ | ㅤ | ㅤ | ㅤ |
协程的利与弊
代码风格更易于理解,代码复用更方便
协程最大的问题就是无法利用CPU多核
协程应用场景
一个线程内的多个协程是串行执行的,不能利用多核,所以,显然,协程不适合计算密集型的场景。协程适合I/O 阻塞型。
I/O本身就是阻塞型的(相较于CPU的时间世界而言)。就目前而言,无论I/O的速度多快,也比不上CPU的速度,所以一个I/O相关的程序,当其在进行I/O操作时候,CPU实际上是空闲的。
我们假设这样的场景,如下图:1个线程有5个I/O的事情(子程序)要处理。如果我们绝对的串行化,那么当其中一个I/O阻塞时,其他4个I/O并不能得到执行,因为程序是绝对串行的,5个I/O必须一个一个排队等待处理,当一个I/O阻塞时,其它4个也得等着。
关于 State-Thread 的一个问题思考
关于st源码分析-运行协程文章 阅读中的一个思考,文中指出 State-Thread 在执行restoreWork的时候,在内部封装recevfrom,函数,主要是在idle主协程中,判断切换所有协程的sendto,发送域名查询请求后,再切换到idl来进行阻塞,其根本原理同手动执行所有sendto,再执行select/epoll一样,只是协程的st_sendto跟st_recvfrom看起来是顺序执行的。
如果说state thread 协程其实本质是对系统的select /epoll 进行封装的话,那么在什么时候进行切换是由库决定还是必须人工想好每个切换点,然后利用协程的切换去做这个事情。
除了 idle 协程,其他所有协程做的事情都是对于 在 系统线程 看来都是非阻塞,非阻塞协程干的话,就是把 需要监听的 fd 放进去全局变量 fd_set,然后由 idle 协程 执行 select() 函数阻塞,等待信号。ST的调度策略是 _ST_RUNQ 里面已经没有协程要运行了,才会切换到 idle 协程来阻塞。
State-Thread——Sleep
由于协程实际上是在单线程中运行,如果需要sleep而调用系统的sleep或者usleep的话会阻塞整个线程,所以协程内部需要实现自己的sleep, state-Thread是这样做的:
st_usleep(st_utime_t usecs)
_st_idle_thread_start(void *arg)
- 通过 引入一个协程状态 ST_ST_SLEEPING
- 将a协程加入
SLEEPQ
队列中,然后使用_ST_SWITCH_CONTEXT()
切换其他协程,此时协程a的状态相当于挂起
- 在最终的
_st_idle_thread_start()
中,通过_st_vp_check_clock()
处理所有进入_ST_ST_SLEEPING
状态的协程,通过比对a协程加入SLEEPQ队列的时间值是否满足睡眠时间来做出下一步决断。