• Windows核心编程 第十二章 纤程


    1 2章 纤 程

        M i c r o s o f t公司给Wi n d o w s添加了一种纤程,以便能够非常容易地将现有的 U N I X服务器应用程序移植到Wi n d o w s中。U N I X服务器应用程序属于单线程应用程序(由 Wi n d o w s定义) ,但是它能够为多个客户程序提供服务。换句话说, U N I X应用程序的开发人员已经创建了他们自己的线程结构库,他们能够使用这种线程结构库来仿真纯线程。该线程包能够创建多个堆栈,

    保存某些C P U寄存器,并且在它们之间进行切换,以便为客户机请求提供服务。

        显然,若要取得最佳的性能,这些 U N I X应用程序必须重新设计,仿真的线程库应该用Wi n d o w s提供的纯线程来替代。然而,这种重新设计需要花费数月甚至更长的时间才能完成,因此许多公司首先将它们现有的 U N I X代码移植到Wi n d o w s中,这样就能够将某些应用软件推向Wi n d o w s市场。

        当你将U N I X代码移植到Wi n d o w s中时,一些问题就会因此而产生。尤其是 Wi n d o w s管理线程的内存栈的方法要比简单地分配内存复杂得多。 Wi n d o w s内存栈开始时的物理存储器的容量比较小,然后根据需要逐步扩大。这个过程在第 1 6章“线程的堆栈”中详细介绍。由于结构化异常处理机制的原因,代码的移植就更加复杂了。

    为了能够更快和更正确地将它们的代码移植到 Wi n d o w s中,M i c r o s o f t公司在操作系统中添加了纤程。本章将要介绍纤程的概念、负责操作纤程的函数以及如何利用纤程的特性。要记住,如果有设计得更好的使用Wi n d o w s自身线程的应用程序,那么应该避免使用纤程。

    纤程的操作

      首先要注意的一个问题是,实现线程的是Wi n d o w s内核。操作系统清楚地知道线程的情况,并且根据M i c r o s o f t定义的算法对线程进行调度。纤程是以用户方式代码来实现的,内核并不知道纤程,并且它们是根据用户定义的算法来调度的。由于你定义了纤程的调度算法,因此,就内核而言,纤程采用非抢占式调度方式。

    需要了解的下一个问题是,单线程可以包含一个或多个纤程。就内核而言,线程是抢占调度的,是正在执行的代码。然而,线程每次执行一个纤程的代码 — 你决定究竟执行哪个纤程(随着我们讲解的深入,这些概念将会越来越清楚) 。

     当使用纤程时,你必须执行的第一步操作是将现有的线程转换成一个纤程。可以通过调用C o n v e r t T h r e a d To F i b e r函数来执行这项操作:

    PVOID ConvertThreadToFiber(PVOID pvParam);

        该函数为纤程的执行环境分配相应的内存(约为2 0 0字节) 。该执行环境由下列元素组成:

    • 一个用户定义的值,它被初始化为传递给C o n v e r t T h r e a d To F i b e rp v P a r a m参数的值。

    • 结构化异常处理链的头。

    • 纤程内存栈的最高和最低地址(当将线程转换成纤程时,这也是线程的内存栈) 。

    CPU寄存器,包括堆栈指针、指令指针和其他。

      当对纤程的执行环境进行分配和初始化后,就可以将执行环境的地址与线程关联起来。该线程被转换成一个纤程,而纤程则在该线程上运行。 C o n v e r t T h r e a d To F i b e r函数实际上返回纤程的执行环境的内存地址。虽然必须在晚些时候使用该地址,但是决不应该自己对该执行环境数据进行读写操作,因为必要时纤程函数会为你对该结构的内容进行操作。现在,如果你的纤程(线程)返回或调用E x i t T h r e a d函数,那么纤程和线程都会终止运行。

      除非打算创建更多的纤程以便在同一个线程上运行,否则没有理由将线程转换成纤程。若要创建另一个纤程,该线程(当前正在运行纤程的线程)可以调用 C r e a t e F i b e r函数:

    PVOID CreateFiber(

        DWORD dwStackSize,

        PFIBER_START_POUTINE pfnStartAddress,

        PVOID pvParam);

      C r e a t e F i b e r首先设法创建一个新内存栈,它的大小由d w S t a c k S i z e参数来指明。通常传递的参数是0,按照默认设置,它创建一个内存栈,其大小可以扩展为 1 M B,不过开始时有两个存储器页面用于该内存栈。如果设定一个非0值,那么就用设定的大小来保存和使用内存栈。接着,C r e a t e F i b e r函数分配一个新的纤程执行环境结构,并对它进行初始化。该用户定义的值被设置为传递给C r e a t e F i b e rp v P a r a m参数的值,新内存栈的最高和最低地址被保存,同时,纤程函数的内存地址(作为p f n S t a r t A d d r e s s参数来传递)也被保存。P f n S t a r t A d d r e s s参数用于设定必须实现的纤程例程的地址,它必须采用下面的原型:

    VOID WINAPI FiberFunc(PVOID pvParam);

      当纤程被初次调度时,该函数就开始运行,并且将原先传递给 C r e a t e F i b e rp v P a r a m的值传递给它。可以在这个纤程函数中执行想执行的任何操作。但是该函数的原型规定返回值是V O I D,这并不是因为返回值没有任何意义,而是因为该函数根本不应该返回。如果纤程确实返回了,那么线程和该线程创建的所有纤程将立即被撤消。

    C o n v e r t T h r e a d To F i b e r函数一样,C r e a t e F i b e r函数也返回纤程运行环境的内存地址。 但是,与C o n v e r t T h r e a d To F i b e r不同的是,这个新纤程并不执行,因为当前运行的纤程仍然在执行。在单个线程上,每次只能运行一个纤程。若要使新纤程能够运行,可以调用 Switch To Fiber函数:

    VOID SwitchToFiber(PVOID pvFiberExecutionContext);

    Switch To Fiber函数只有一个参数,即 p v F i b e r E x e c u t i o n C o n t e x t,它是上次调用C o n v e r t T h r e a d To F i b e rC r e a t e F i b e r函数时返回的纤程的执行环境的内存地址。该内存地址告诉该函数要对哪个纤程进行调度。S w i t c h To F i b e r函数在内部执行下列操作步骤:

    1) 它负责将某些当前的C P U寄存器保存在当前运行的纤程执行环境中,包括指令指针寄存器和堆栈指针寄存器。

    2) 它将上一次保存在即将运行的纤程的执行环境中的寄存器装入 C P U寄存器。这些寄存器包括堆栈指针寄存器。这样,当线程继续执行时,就可以使用该纤程的内存栈。

    3) 它将纤程的执行环境与线程关联起来,线程运行特定的纤程。

    4) 它将线程的指令指针设置为已保存的指令指针。线程(纤程)从该纤程上次执行的地方开始继续执行。

      S w i t c h To F i b e r函数是纤程获得C P U时间的唯一途径。由于你的代码必须在相应的时间显式调用S w i t c h To F i b e r函数,因此你对纤程的调度可以实施全面的控制。记住,纤程的调度与线程调度毫不相干。纤程运行所依赖的线程始终都可以由操作系统终止其运行。当线程被调度时,当前选定的纤程开始运行,而其他纤程则不能运行,除非显式调用 S w i t c h To F i b e r函数。若要撤消纤程,可以调用D e l e t e F i b e r函数:

    VOID DeleteFiber(PVOID pvFiberExecutionContext);

      该函数用于删除p v F i b e r E x e c u t i o n C o n t e x t参数指明的纤程,当然这是纤程的执行环境的地址。该函数能够释放纤程栈使用的内存,然后撤消纤程的执行环境。但是,如果传递了当前与线程相关联的纤程地址,那么该函数就在内部调用 E x i t T h r e a d函数,该线程及其创建的所有纤程全部被撤消。

      D e l e t e F i b e r函数通常由一个纤程调用,以便删除另一个纤程。已经删除的纤程的内存栈将被撤消,纤程的执行环境被释放。注意,纤程与线程之间的差别在于,线程通常通过调用E x i t T h r e a d函数将自己撤消。实际上,用一个线程调用 Te r m i n a t e T h r e a d函数来终止另一个线程的运行,是一种不好的方法。如果你确实调用了 Te r m i n a t e T h r e a d函数,系统并不撤消已经终止

    运行的线程的内存栈。可以利用纤程的这种能力来删除另一个纤程,后面介绍示例应用程序时将说明这是如何实现的。

        为了使操作更加方便,还可以使用另外两个纤程函数。一个线程每次可以执行一个纤程,操作系统始终都知道当前哪个纤程与该线程相关联。如果想要获得当前运行的纤程的执行环境的地址,可以调用G e t C u r r e n t F i b e r函数:

         PVOID GetCurrentFiber();

      另一个使用非常方便的函数是G e t F i b e r D a t a

         PVOID GetFiberData();

      前面讲过,每个纤程的执行环境包含一个用户定义的值。这个值使用作为 C o n v e r t T h r e a dTo F i b e rC r e a t e F i b e rp v P a r a m参数而传递的值进行初始化。该值也可以作为纤程函数的参数来传递。G e t F i b e r D a t a只是查看当前执行的纤程的执行环境,并返回保存的值。

      无论G e t C u r r e n t F i b e r还是G e t F i b e r D a t a,运行速度都很快,并且通常是作为内蕴函数(infrinsic funcfion)来实现的,这意味着编译器能够为这些函数生成内联代码。

     

  • 相关阅读:
    Core Animation 文档翻译—附录C(KVC扩展)
    Core Animation 文档翻译—附录B(可动画的属性)
    Core Animation 文档翻译—附录A(Layer样貌相关属性动画)
    Core Animation 文档翻译 (第八篇)—提高动画的性能
    Core Animation 文档翻译 (第七篇)—改变Layer的默认动画
    Core Animation 文档翻译 (第六篇)—高级动画技巧
    Core Animation 文档翻译 (第五篇)—构建Layer的层次结构
    用Markdown快速排版一片文章
    Core Animation 文档翻译 (第四篇)—让Layer的content动画起来
    Core Animation 文档翻译(第三篇)—设置Layer对象
  • 原文地址:https://www.cnblogs.com/csnd/p/12062219.html
Copyright © 2020-2023  润新知