zoukankan      html  css  js  c++  java
  • 纤程(Fiber) [转]

    纤程(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、线程结束

  • 相关阅读:
    Day 20 初识面向对象
    Day 16 常用模块
    Day 15 正则表达式 re模块
    D14 模块 导入模块 开发目录规范
    Day 13 迭代器,生成器,内置函数
    Day 12 递归,二分算法,推导式,匿名函数
    Day 11 闭包函数.装饰器
    D10 函数(二) 嵌套,命名空间作用域
    D09 函数(一) 返回值,参数
    Day 07 Day08 字符编码与文件处理
  • 原文地址:https://www.cnblogs.com/viviwind/p/2821824.html
Copyright © 2011-2022 走看看