Windows Via C/C++ 读书笔记 8 Fiber(纤程)

来源:互联网 发布:ae视频软件 编辑:程序博客网 时间:2024/05/28 17:06

Windows Via C/C++ 读书笔记 

Fiber(纤程)

1. Overview

本章讲UNIX多线程和Windows线程区别,和Fibers的相似。Unix部分是我查资料加个人理解写的,可能有些错误,请达人指正。

UNIX操作系统的多线程实现。这章比较短,查了下UNIX线程的实现。以前知道UNIX只有进程的概念,后来工作又接触了pthreads多线程编程,一直没搞明白。看了下UNIX技术内幕,大概了解了一些,可能还有很多理解不到位的地方,请大家指正。pthreads的实现与fibers非常相似,微软实现fibers的目的就是为了UNIX程序能够快速的过渡过来。

前面章节已经讲了,windows本身是多进程多线程的操作系统,进程和线程都是内核对象。进程间使用不同的内存区域,进程内的线程共享一块内存区域,可以互相访问,而不需要通过复杂的IPC,效率很高。操作系统内核负责线程的调度和管理。

UNIX本身是只有进程的概念,后来UNIX实现提出了2元分化并行。即操作系统提供内核线程和线程库2种多线程方案。在内核线程的基础上又提供了轻量级进程(LWP)。还有一种在进程上模拟多线程的库方案,比如POSIX pthreads库。

线程库是由库提供创建、管理、调度线程的API(提供类似windows线程的API,如sleep等),工作在用户态,由库来维护线程的上下文(寄存器状态,栈地址),对内核来讲是透明的,内核只能看到多线程所在的进程。线程库实际上就是在操作系统之上实现了一个多线程的微内核,它模拟出了一个多线程的工作环境。这个和后面讲的fibers实现非常类似,可以互相参考理解。它们的区别是fibers完全靠用户自己用switch函数调度当前哪个fiber进入运行状态,而线程库会让你在写程序的时候感觉跟在windows下写多线程程序差不多,库替你完成了调度工作。缺点就是,一旦有个线程调用了操作系统的wait操作,那么整个进程都被阻塞,而不是一个线程被阻塞。

内核线程是内核根据需要创建和使用的,完全在内核中。它共享内核正文字段和全局数据。(这段是我抄下来的,我也不太理解。我感觉可以把内核想象成一个进程,它为了完成多线程工作创建管理了内核线程,这是真正意义上的线程,但是跟咱用户没啥关系。后面我找时间看些UNIX深入的书籍,再完善这块内容)。

LWP轻量级进程 LWP必须内核提供内核线程支持。一个进程可以有多个LWP,每个LWP共享进程的资源(看起来就是Windows真正的线程),每个LWP和一个内核线程对应(这是与Windows线程的区别)。我的理解是,当LWP执行时,操作系统会从线程池选择一个或者创建一个内核线程,把LWP的参数拷贝到内核线程中,然后由内核线程完成实际的工作,因此,LWP的阻塞是不会阻塞进程,这是比线程库实现的优势。当LWP需要等待时,操作系统把内核线程中的参数、上下文信息、内存堆栈都拷贝回LWP,然后停止执行。当LWP需要重新执行的时候,再把参数和上下文信息拷贝回内核线程,内核线程又能恢复运行。LWP是用户态,内核线程是内核态,因此这个切换工作开销非常大,需要保存资源的内存也需要很多。

这篇文章讲得很好,就是UNIX技术内幕第三章的内容,带上了图片:http://www.cnitblog.com/tarius.wu/articles/2277.html

2. Fibers

Windows内核实现线程,fibers实现时在用户模式。线程由操作系统根据算法调度,fibers由编程人员编写代码调度。

Fibers看起来就是一个轻量级线程的实现,但有的人说这种说法不对。到底怎么才算是轻量级线程呢?只看到Erlang有轻量级线程的说法,其它地方都没有,这个概念暂时放一边好了。

一个线程可以有一个或多个fibersFibers都是在一个线程中执行,在操作系统看来,它只能看到线程,只对线程做调度,线程是调度的基本单位。因此,同一个时间只会有一个fiber在执行。如果线程中的一个fiber进入阻塞状态,整个线程进入阻塞状态。参考UNIX线程库,两者在这方面差不多。

编写fibers程序的典型模式/步骤:

1. 线程把自己由线程转换为一个fiber,创建出第一个fiber

PVOID ConvertThreadToFiber(PVOID pvParam);

2. 再由这个线程(现在是fiber)创建其它的fiber

PVOID CreateFiber(

   DWORD dwStackSize,

   PFIBER_START_ROUTINE pfnStartAddress,

   PVOID pvParam);

第一个fiber会继续执行转换函数后面的代码,以后创建的fiber需要传递一个函数地址给CreateFiber函数,作为执行入口。函数返回一个fiber上下文句柄,提供给控制函数使用。

3. 切换到新的fiber执行。

VOID SwitchToFiber(PVOID pvFiberExecutionContext);

这个函数可以在任何fiber中调用,调用后,调用者停止执行,pvFiberExcutionContext代表的fiber开始运行。

4. 切换回第一个fiber,准备结束线程。

在各个fiber调用后,某个fiber执行SwitchToFiber函数切换回第一个fiber中。因为第一个fiber是线程,要结束运行还是得从这里结束会比较好,当然也可以从其它fiber结束。任何一个fiber运行结束会使线程结束。但是那样不能很好的释放fiber的资源(stack 和 context)。

5. 删除已经创建的fiber(不包括第一个fiber,第一个fiber是线程转换得到)。

VOID DeleteFiber(PVOID pvFiberExecutionContext);

6. 把第一个fiber转换回线程,线程继续执行到结束。

ConverFiberToThread

3. 小结

Fiber在绝大多数情况下都是不推荐使用的,因为它的调度完全靠程序员做(switch函数切换),在阻塞情况下会阻塞所有的fibers。因此,尽量使用线程来解决并行问题。

我是在TopLanguage的精华区看到某个达人想用fiber实现一个类似Erlang轻量级线程的框架才对这个东西感兴趣,也许研究了Erlang以后会对这个东西有更深刻的了解。

我认为,它与线程比唯一的好处就是它所有的切换工作都在用户态,而线程是工作在内核态,消耗比线程小。而带来的麻烦是很多很多。还有一个好处就是同一个线程里面的fiber是不需要做同步互斥的,因为任何一个时间都只有一个fibercpu中执行(线程是基本运行单位)。

原创粉丝点击