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: /seizef/article/details/6567301
Linux
Linux中有context api在中,可以认为是一个先进的版本 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异常链,等等。