Windows核心编程笔记(十二) 纤程

纤程(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、线程结束
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值