博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
Linux下c实现协程-Coroutine
阅读量:2340 次
发布时间:2019-05-10

本文共 2111 字,大约阅读时间需要 7 分钟。

Widnows 是提供了用户级线程的,类似 coroutine 需要用户主动是切换。这在单线程程序中非常有用。线程调度模块只负责提供堆栈,环境的保存。不负责分配时间片等。
自己实现 coroutine 并不难,但能用操作系统提供的可以得到更多的便利。Windows 中把这种用户级线程叫做 Fiber,纤维的意思。比较通用的译名是纤程。
我们可以把一个 thread 转换成一个 fiber ,用到的 API 是 ConvertThreadToFiber。其实用的更多的是CreateFiber,它可以创建一个纤程,但并不切换过去运行。
被创建出来的 Fiber 会有一个上下文的地址被返回,用于以后的切换操作。我们可以用 SwitchToFiber 来切换。这是唯一用于 Fiber 释放操作权的途径。SwitchToFiber 必须显式的指定切换的目标,所以 Fiber 调度的工作需要我们自己写代码来实现。
GetCurrentFiber 和 GetFiberData 这两个函数都很有用,一个用来取到运行环境,一个用来取得创建参数,这两个函数都是用 inline 函数的形式提供在 .h 文件中的。
Fiber,也就是纤程,完全运行在用户态,各个线程的切换也只在用户态完成避免了系统调用,所以切换开销较小。线程的调度,通常是由操作系统的线程调度器完成,在现代OS中,通常使用抢占式调度策略。而纤程的调用,完全依赖于程序员自己,即实现一种合作式调度,只有在主动提出切换时,才会进行切换。
多线程的程序很多不是为了提高效率,充分利用多 CPU,而是为了逻辑描述方便。
纤程往往可以提供更加便捷的描述,因为它只能通过 SwitchToFiber 显式切换,这样可以少许多加解锁的过程。而且切换的效率也比真正的线程来的高。
Fiber 就是一种 coroutine 的实现,而 coroutine 其实是一种常见的算法表达方式。只是 C 语言和其衍生的基于一个环境堆栈的语言并没有直接支持。所以才少为人知。
纤程用于化异步为同步。
你可以进行一个异步操作以后就切换纤程,等到异步操作完成以后在切换回来,这样,在逻辑上相关的代码就可以写到一个函数里面,而不用人为的分到多个回调函数中
fiber 的好处应该可以减轻锁的顾虑。在没有主动切换上下文之间,可以看成是原子操作。
要自己实现fiber,主要是保存线程上下文。注意要保存的信息有:各个寄存器,栈顶和栈底,异常信息,浮点寄存器。
window Fiber的默认堆栈是1M
How to implement fiber: http://blog.csdn.net/seizef/article/details/6567301
Linux
Linux中有context api在ucontext.h中,可以认为是一个先进的版本 setjmp/longjmp, 可以用来完成类似的功能。我们调用getcontext/makecontext,来初始化纤程,用swapcontext/setcontext来切换纤程。
makecontext 创建一个新的上下文,可以传入我们自己定义的栈空间。它记录CPU的各种信息
swapcontext 切换上下文(会保存当前上下文并切换到另一个上下文),其实就是改变CPU的相关寄存器 状态,替换到另外的可执行的上下文中
getcontext用于保存当前上下文,
setcontext用于切换上下文
实现用户线程的过程是:我们首先调用getcontext获得当前上下文,然后修改ucontext_t指定新的上下文。同样的,我们需要开辟栈空间,但是这次实现的线程库要涉及栈生长的方向。然后我们调用makecontext切换上下文,并指定用户线程中要执行的函数。
还有一个挑战,即一个线程必须可以主动让出CPU给其它线程。swapcontext函数可以完成这个任务
setjmp/longjmp
说到这里,我们不得不提起另一个看似可以用来完成类似工作的古老的C API,setjmp/longjmp。setjmp用来保存当前的执行环境,longjmp用来还原上次的执行,这样可以实现non-local goto的功能。但是这里有一个问题,就是当我们调用longjmp回到setjmp保存的状态继续执行时,如果longjmp的调用者与setjmp的调用者不是同一个函数,那么longjmp所在的栈的状态将是undefined[1]。也就是说,我们不能通过在longjmp之前,save状态,之后再longjmp回来,这是未定义的行为。当然,在win32上,你可以这么做,而且还工作地很好(我在实现中使用了这一技巧)。但是在linux上,你将会遇到运行时错误,提示stack smashing(gcc的stack保护机制,__fortify_fail),也就是栈被破坏了(这令我debug了很长时间,最后放弃,网上看到许多实现用这方法,但是不奏效)。setjmp/longjmp还有其他陷阱,比如,在win32上,他不会保存/恢复SEH异常链,等等。
你可能感兴趣的文章
x264中的DCT变换 dct-a.asm
查看>>
X264的时耗分析
查看>>
H.264 Profile、Level、Encoder三张简图
查看>>
NEON指令集综述
查看>>
FFmpeg的H.264解码器源代码简单分析:概述
查看>>
linux下编译调试x264
查看>>
debug和release版本的区别
查看>>
x86 指令集发展历程
查看>>
逐行Porgressive隔行Interlaced扫描的超详细讲解
查看>>
使用FFmpeg实现抠图合并功能(chroma key)
查看>>
长宽比 (视频)
查看>>
Pan & Scan和Letterbox
查看>>
资深影迷不可不知的宽高比:Aspect Ratio 电影画面比例
查看>>
MacBook Pro 外接显示器设置竖屏
查看>>
X264的参考帧设置
查看>>
三种帧的说明
查看>>
感知视频编码
查看>>
深度学习 vs 机器学习 vs 模式识别
查看>>
Tone mapping进化论
查看>>
XAVC
查看>>