导航

纤程(Fiber) [转]

Posted on 2012-12-17 15:52  vivi_wind  阅读(263)  评论(0编辑  收藏  举报

纤程(Fiber),是微软加入到Windows中,使得UNIX服务器应用程序更好地移植到Windows中。所以本篇真正没有多少应用价值,只是为了使得笔记更加完整。

 

  看完本章,感觉纤程是比线程的更小的一个运行单位。可以把一个线程拆分成多个纤程,然后通过人工转换纤程,从而让各个纤程工作。

  要知道的是人工的转换,不是系统自动切换。因为线程的实现通过Windows内核完成的,因此Windows可以自动对线程进行调度。但是纤程是通过用户模式的代码来实现的,是程序员自己写的算法,内核不知道纤程的实现方式,而是你自己定义的调度算法,因此纤程是“非抢占”的调度方式。

  还有要知道就是,一个线程可以包含多个纤程。

 

  要使用纤程,首先要做的就是把当前线程转换为纤程:

PVOID ConvertThreadToFiber(PVOID pvParam);

 

  调用这个函数之后,系统为纤程执行环境分配大概200字节的存储空间,这个执行环境有以下内容构成:

1、用户定义的值,由参数pvParam参数指定。

2、结构化异常处理链头。

3、纤程内存栈的最高和最低地址,当线程转换为纤程的时候,这也是线程的内存栈。

4、各种CPU寄存器信息,比如堆栈指针寄存器,指令指针寄存器等等。

 

  默认情况下,x86系统的CPU的浮点数状态信息在纤程看来不属于CPU寄存器,因此会导致在纤程中执行一些相关的浮点运算会破坏数据。为了克服这个缺点,你需要呼叫ConvertThreadToFiberEx函数(Windows Vista及其以上版本中才有),并且传递FIBER_FLAG_FLOAT_SWITCH给它的第2个参数dwFlags:

PVOID ConvertThreadToFiberEx( PVOID pvParam, DWORD dwFlags);

 

  当呼叫完上述两个函数之后,你就初始化了一个纤程执行环境,该执行环境与线程的执行环境关联,线程转换为纤程,纤程就在线程的内部运行。ConvertThreadToFiber(Ex)函数实际返回纤程的执行环境的内存地址,你稍后会用到这个地址,但是你不能直接读取或写入这个地址,你应该使用系统提供的纤程函数来对这个地址进行操纵。

  当你的纤程返回或者呼叫ExitThread的时候,你的纤程也随之结束。

 

  如果一个线程中只有一个纤程,那么是没有必要将该线程转换为纤程的,只有你打算在同一个线程中再创建一个纤程才有转换的必要。要创建一个纤程,使用CreateFiber函数:

PVOID CreateFiber( DWORD dwStackSize, // 创建新的堆栈的大小,0表示默认大小 PFIBER_START_ROUTINE pfnStartAddress, // 纤程函数地址 PVOID pvParam); // 传递给纤程函数的参数

 

  这个函数创建一个新的堆栈,堆栈的大小由dwStackSize指定。如果传递0给它,就意味着创建一个默认大小的堆栈。

  如果你打算让一个线程包含多个纤程,而又想花费比较少的空间的话,可以使用CreateFiberEx函数(只有在Windows Vista及其以上版本中才有):

PVOID CreateFiberEx( SIZE_T dwStackCommitSize, // 堆栈初始提交的大小 SIZE_T dwStackReserveSize, // 需要保留的虚拟内存的大小 DWORD dwFlags, // 创建旗标 PFIBER_START_ROUTINE pStartAddress, // 纤程函数指针 PVOID pvParam); // 传递给纤程函数的参数

 

  其中,如果传递FIBER_FLAG_FLOAT_SWITCH给dwFlags参数,则表明将浮点信息添加到纤程执行环境。

 

  当CreateFiber(Ex)函数创建了一个新的堆栈之后,它分配一个新的纤程执行环境结构并初始化之,用户定义的数据通过pvParam参数被保存,新的堆栈的内存空间的最高和最低地址被保存,纤程函数的地址通过pStartAddress参数被保存。

  纤程函数的格式必须如下定义:

VOID WINAPI FiberFunc(PVOID pvParam);

  这个纤程在第一次被调度的时候,纤程函数被调用,其参数pvParam由CreateFiber(Ex)中的pvParam参数指定。在纤程函数中,你可以做你想做的任何事情。

  像ConvertThreadToFiber(Ex)函数一样,CreateFiber(Ex)也返回纤程执行环境的内存地址,这个内存地址就像句柄一样,直接标识着一个纤程。

  当你使用CreateFiber(Ex)函数创建一个纤程之后,该纤程不会执行,因为系统不会自动调度它。你必须调用函数SwitchToFiber来告诉系统你想要哪个纤程执行:

VOID SwitchToFiber(PVOID pvFiberExecutionContext);

 

  SwitchToFiber函数的参数是一个纤程执行环境的内存地址,该地址由ConverThreadToFiber(Ex)或CreateFiber(Ex)返回。

  SwitchToFiber函数内部的执行步骤如下:

1、保存当前的CPU寄存器信息,这些信息保存在正在运行的纤程的执行环境中。

2、从将要执行的纤程的执行环境中加载上次保存的CPU寄存器信息。

3、将即将执行的纤程执行环境与线程关联起来,由线程执行指定的纤程。

4、将指令指针设置为保存的值,继续上次的执行。

 

  SwitchToFiber函数是一个纤程能够被调度的唯一的方法,因此,纤程的调度是由用户完全操纵的。纤程的调度和线程的调度无关。一个线程,包含了正在运行的纤程,仍会被其他线程抢占。当一个线程被调度,而它里面有几个纤程,那么只有被选择的那个纤程才会执行,其他纤程的执行需要调用SwitchToFiber函数。

 

  最后,如果一个纤程完成了任务,你需要删除它,呼叫DeleteFiber函数,并传递这个纤程的执行环境内存地址:

VOID DeleteFiber(PVOID pvFiberExecutionContext);

 

  该函数首先清除纤程堆栈,然后删除纤程执行环境。但是,如果参数指定的是一个与当前线程关联的纤程,该函数呼叫ExitThread函数,线程结束,其包含的其他纤程也都结束。因此,DeleteFiber函数一般是由一个纤程调用来删除另一个纤程。

  当所有纤程结束了运行,你需要从纤程转换为线程,呼叫ConvertFiberToThread函数。

 

  如果你需要在纤程中保存一些数据,可以使用“纤程局部存储”(FLS)的机制。这个机制和“线程局部存储”(TLS)类似。

  首先,呼叫FlsAlloc函数分配FLS槽来存放数据,这个FLS槽可以被当前进程内所有纤程共同使用,函数有一个参数:一个回调函数指针,这个回调函数会在以下两种情况下被调用:一个纤程被删除;FLS槽通过FlsFree函数被删除。

  然后,在你呼叫FlsAlloc函数之后,你可以在纤程中使用FlsSetValue函数来保存数据到FLS槽中,同时该函数需要一个DWORD类型的参数,表示一个FLS槽的索引,即在FLS槽的相关地方保存数据。

  接着,你可以在各个纤程中使用FlsGetValue函数来取得FLS槽中对应的数据,同样需要上面那个FLS槽索引,并返回指向数据的指针。

  当使用完这些数据之后,你可以使用FlsFree来释放FLS槽。

 

  如果你想知道你是否正在一个纤程执行环境中运行,可以使用IsThreadAFiber函数,它返回一个BOOL值,指明你是否正在一个纤程中运行。

 

  一个线程每次只能执行一个纤程,该纤程与这个线程相关联。你可以使用如下函数来得到正在执行的纤程的执行环境内存地址:

PVOID GetCurrentFiber();

 

  每个纤程包含用户定义的一个数据,这个数据由CreateFiber(Ex)或ConvertThreadToFiber(Ex)的pvParam参数指定,你可以使用如下函数得到这个数据的指针:

PVOID GetFiberData();

 

  最后,让我们假设一个线程中有2个纤程,总结一下纤程的用法:

1、使用ConverThreadToFiber(Ex)将当前线程转换到纤程,这是纤程F1

2、定义一个纤程函数,用于创建一个新纤程

3、纤程F1中调用CreateFiber(Ex)函数创建一个新的纤程F2

4、SwitchToFiber函数进行纤程切换,让新创建的纤程F2执行

5、F2纤程函数执行完毕的时候,使用SwitchToFiber转换到F1

6、在纤程F1中调用DeleteFiber来删除纤程F2

7、纤程F1中调用ConverFiberToThread,转换为线程

8、线程结束