windows核心编程指南09.pdf

上传人:asd****56 文档编号:70322232 上传时间:2023-01-19 格式:PDF 页数:38 大小:1.75MB
返回 下载 相关 举报
windows核心编程指南09.pdf_第1页
第1页 / 共38页
windows核心编程指南09.pdf_第2页
第2页 / 共38页
点击查看更多>>
资源描述

《windows核心编程指南09.pdf》由会员分享,可在线阅读,更多相关《windows核心编程指南09.pdf(38页珍藏版)》请在taowenge.com淘文阁网|工程机械CAD图纸|机械工程制图|CAD装配图下载|SolidWorks_CaTia_CAD_UG_PROE_设计图分享下载上搜索。

1、下载第9章线程与内核对象的同步上一章介绍了如何使用允许线程保留在用户方式中的机制来实现线程同步的方法。用户方式同步的优点是它的同步速度非常快。如果强调线程的运行速度,那么首先应该确定用户方式的线程同步机制是否适合需要。虽然用户方式的线程同步机制具有速度快的优点,但是它也有其局限性。对于许多应用程序来说,这种机制是不适用的。例如,互锁函数家族只能在单值上运行,根本无法使线程进入等待状态。可以使用关键代码段使线程进入等待状态,但是只能用这些代码段对单个进程中的线程实施同步。还有,使用关键代码段时,很容易陷入死锁状态,因为在等待进入关键代码段时无法设定超时值。本章将要介绍如何使用内核对象来实现线程的

2、同步。你将会看到,内核对象机制的适应性远远优于用户方式机制。实际上,内核对象机制的唯一不足之处是它的速度比较慢。当调用本章中提到的任何新函数时,调用线程必须从用户方式转为内核方式。这个转换需要很大的代价:往返一次需要占用 x 8 6平台上的大约1 0 0 0个C P U周期,当然,这还不包括执行内核方式代码,即实现线程调用的函数的代码所需的时间。本书介绍了若干种内核对象,包括进程,线程和作业。可以将所有这些内核对象用于同步目的。对于线程同步来说,这些内核对象中的每种对象都可以说是处于已通知或未通知的状态之中。这种状态的切换是由M i c r o s o f t为每个对象建立的一套规则来决定的。

3、例如,进程内核对象总是在未通知状态中创建的。当进程终止运行时,操作系统自动使该进程的内核对象处于已通知状态。一旦进程内核对象得到通知,它将永远保持这种状态,它的状态永远不会改为未通知状态。当进程正在运行的时候,进程内核对象处于未通知状态,当进程终止运行的时候,它就变为已通知状态。进程内核对象中是个布尔值,当对象创建时,该值被初始化为 FA L S E(未通知状态)。当进程终止运行时,操作系统自动将对应的对象布尔值改为 T R U E,表示该对象已经得到通知。如果编写的代码是用于检查进程是否仍在运行,那么只需要调用一个函数,让操作系统去检查进程对象的布尔值,这非常简单。你也可能想要告诉系统使线程

4、进入等待状态,然后当布尔值从FA L S E改为T R U E时自动唤醒该线程。这样,你可以编写一个代码,在这个代码中,需要等待子进程终止运行的父进程中的线程只需要使自己进入睡眠状态,直到标识子进程的内核对象变为已通知状态即可。你将会看到,M i c r o s o f t的Wi n d o w s提供了一些能够非常容易地完成这些操作的函数。刚才讲了M i c r o s o f t为进程内核对象定义了一些规则。实际上,线程内核对象也遵循同样的规则。即线程内核对象总是在未通知状态中创建。当线程终止运行时,操作系统会自动将线程对象的状态改为已通知状态。因此,可以将相同的方法用于应用程序,以确定线

5、程是否不再运行。与进程内核对象一样,线程内核对象也可以处于已通知状态或未通知状态。下面的内核对象可以处于已通知状态或未通知状态:进程文件修改通知线程事件作业可等待定时器文件信标控制台输入互斥对象线程可以使自己进入等待状态,直到一个对象变为已通知状态。注意,用于控制每个对象的已通知/未通知状态的规则要根据对象的类型而定。前面已经提到进程和线程对象的规则及作业的规则。本章将要介绍允许线程等待某个内核对象变为已通知状态所用的函数。然后我们将要讲述Wi n d o w s提供的专门用来帮助实现线程同步的各种内核对象、如事件、等待计数器,信标和互斥对象。当我最初开始学习这项内容时,我设想内核对象包含了一

6、面旗帜(在空中飘扬的旗帜,不是耷拉下来的旗帜),这对我很有帮助。当内核对象得到通知时,旗帜升起来;当对象未得到通知时,旗帜就降下来(见图9-1)。当线程等待的对象处于未通知状态(旗帜降下)中时,这些线程不可调度。但是一旦对象变为已通知状态(旗帜升起),线程看到该标志变为可调度状态,并且很快恢复运行(见图9-2)。图9-1 内核对象中的旗帜状态图9-2 内核对象中旗帜状态与线程可调度性示意图9.1 等待函数等待函数可使线程自愿进入等待状态,直到一个特定的内核对象变为已通知状态为止。这些等待函数中最常用的是Wa i t F o r S i n g l e O b j e c t:第 9章 线程与内

7、核对象的同步计计191下载内核对象内核对象内核对象内核对象当线程调用该函数时,第一个参数 h O b j e c t标识一个能够支持被通知/未通知的内核对象(前面列出的任何一种对象都适用)。第二个参数d w M i l l i s e c o n d s允许该线程指明,为了等待该对象变为已通知状态,它将等待多长时间。调用下面这个函数将告诉系统,调用函数准备等待到h P r o c e s s句柄标识的进程终止运行为止:第二个参数告诉系统,调用线程愿意永远等待下去(无限时间量),直到该进程终止运行。通常情况下,I N F I N I T E是作为第二个参数传递给Wa i t F o r S i

8、n g l e O b j e c t的,不过也可以传递任何一个值(以毫秒计算)。顺便说一下,I N F I N I T E已经定义为0 x F F F F F F F F(或-1)。当然,传递I N F I N I T E有些危险。如果对象永远不变为已通知状态,那么调用线程永远不会被唤醒,它将永远处于死锁状态,不过,它不会浪费宝贵的C P U时间。下面是如何用一个超时值而不是I N F I N I T E来调用Wa i t F o r S i n g l e O b j e c t的例子:上面这个代码告诉系统,在特定的进程终止运行之前,或者在 5 0 0 0 m s时间结束之前,调用线程不应

9、该变为可调度状态。因此,如果进程终止运行,那么这个函数调用将在不到5 0 0 0 m s的时间内返回,如果进程尚未终止运行,那么它在大约 5 0 0 0 m s时间内返回。注意,不能为d w M i l l i s e c o n d传递0。如果传递了0,Wa i t F o r S i n g l e O b j e c t函数将总是立即返回。Wa i t F o r S i n g l e O b j e c t的返回值能够指明调用线程为什么再次变为可调度状态。如果线程等待的对象变为已通知状态,那么返回值是 WA I T _ O B J E C T _ 0。如果设置的超时已经到期,则返回值

10、是WA I T _ T I M E O U T。如果将一个错误的值(如一个无效句柄)传递给Wa i t F o r S i n g l eO b j e c t,那么返回值将是WA I T _ FA I L E D(若要了解详细信息,可调用G e t L a s t E r r o r)。下面这个函数Wa i t F o r M u l t i p l e O b j e c t s与Wa i t F o r S i n g l e O b j e c t函数很相似,区别在于它允许调用线程同时查看若干个内核对象的已通知状态:d w C o u n t参数用于指明想要让函数查看的内核对象的数量。

11、这个值必须在 1与M A X I M U M _WA I T _ O B J E C T S(在Wi n d o w s头文件中定义为6 4)之间。p h O b j e c t s参数是指向内核对象句柄的数组的指针。可以以两种不同的方式来使用Wa i t F o r M u l t i p l e O b j e c t s函数。一种方式是让线程进入等待状192计计第二部分编程的具体方法下载态,直到指定内核对象中的任何一个变为已通知状态。另一种方式是让线程进入等待状态,直到所有指定的内核对象都变为已通知状态。f Wa i tAl l参数告诉该函数,你想要让它使用何种方式。如果为该参数传递T

12、R U E,那么在所有对象变为已通知状态之前,该函数将不允许调用线程运行。d w M i l l i s e c o n d s参数的作用与它在Wa i t F o r S i n g l e O b j e c t中的作用完全相同。如果在等待的时候规定的时间到了,那么该函数无论如何都会返回。同样,通常为该参数传递 I N F I N I T E,但是在编写代码时应该小心,以避免出现死锁情况。Wa i t F o r M u l t i p l e O b j e c t s函数的返回值告诉调用线程,为什么它会被重新调度。可能的返回值是WA I T _ FA I L E D和WA I T _

13、T I M E O U T,这两个值的作用是很清楚的。如果为 f Wa i tAl l参数传递T R U E,同时所有对象均变为已通知状态,那么返回值是WA I T _ O B J E C T _ 0。如果为f Wa i t A l l传递FA L S E,那么一旦任何一个对象变为已通知状态,该函数便返回。在这种情况下,你可能想要知道哪个对象变为已通知状态。返回值是WA I T _ O B J E C T _ 0与(WA I T _ O B J E C T _ 0+d w C o u n t-1)之间的一个值。换句话说,如果返回值不是WA I T _ T I M E O U T,也不是WA I

14、 T _ FA I L E D,那么应该从返回值中减去WA I T _ O B J E C T _ 0。产生的数字是作为第二个参数传递给Wa i t F o r M u l t i p l e O b j e c t s的句柄数组中的索引。该索引说明哪个对象变为已通知状态。下面是说明这一情况的一些示例代码:如果为f Wa i t A l l参数传递FA L S E,Wa i t F o r M u l t i p l e O b j e c t s就从索引0开始向上对句柄数组进行扫描,同时已通知的第一个对象终止等待状态。这可能产生一些你不希望有的结果。例如,通过将3个进程句柄传递给该函数,你的

15、线程就会等待 3个子进程终止运行。如果数组中索引为0的进程终止运行,Wa i t F o r M u l t i p l e O b j e c t s就会返回。这时该线程就可以做它需要的任何事情,然后循环反复,等待另一个进程终止运行。如果该线程传递相同的 3个句柄,该函数立即再次第 9章 线程与内核对象的同步计计193下载返回WA I T _ O B J E C T _ 0。除非删除已经收到通知的句柄,否则代码就无法正确地运行。9.2 成功等待的副作用对于有些内核对象来说,成功地调用 Wa i t F o r S i n g l e O b j e c t和Wa i t F o r M u

16、l t i p l e O b j e c t s,实际上会改变对象的状态。成功地调用是指函数发现对象已经得到通知并且返回一个相对于WA I T _ O B J E C T _ 0的值。如果函数返回WA I T _ T I M E O U T或WA I T _ FA I L E D,那么调用就没有成功。如果函数调用没有成功,对象的状态就不可能改变。当一个对象的状态改变时,我称之为成功等待的副作用。例如,有一个线程正在等待自动清除事件对象(本章后面将要介绍)。当事件对象变为已通知状态时,函数就会发现这个情况,并将WA I T _ O B J E C T _ 0返回给调用线程。但是就在函数返回之前

17、,该事件将被置为未通知状态,这就是成功等待的副作用。这个副作用将用于自动清除内核对象,因为它是 M i c r o s o f t为这种类型的对象定义的规则之一。其他对象拥有不同的副作用,而有些对象则根本没有任何副作用。进程和线程内核对象就根本没有任何副作用,也就是说,在这些对象之一上进行等待决不会改变对象的状态。由于本章要介绍各种不同的内核对象,因此我们将要详细说明它们的成功等待的副作用。究竟是什么原因使得Wa i t F o r M u l t i p l e O b j e c t s函数如此有用呢,因为它能够以原子操作方式来执行它的所有操作。当一个线程调用Wa i t F o r M

18、u l t i p l e O b j e c t s函数时,该函数能够测试所有对象的通知状态,并且能够将所有必要的副作用作为一项操作来执行。让我们观察一个例子。两个线程以完全相同的方式来调用 Wa i t F o r M u l t i p l e O b j e c t s:当Wa i t F o r M u l t i p l e O b j e c t s函数被调用时,两个事件都处于未通知状态,这就迫使两个线程都进入等待状态。然后h A u t o R e s e t E v e n t 1对象变为已通知状态。两个线程都发现,该事件已经变为已通知状态,但是它们都无法被唤醒,因为 h A

19、 u t o R e s e t E v e n t 2仍然处于未通知状态。由于两个线程都没有等待成功,因此没有对h A u t o R e s e t E v e n t 1对象产生任何副作用。接着,h A u t o R e s e t E v e n t 2变为已通知状态。这时,两个线程中的一个发现,两个对象都变为已通知状态。等待取得了成功,两个事件对象均被置为未通知状态,该线程变为可调度的线程。但是另一个线程的情况如何呢?它将继续等待,直到它发现两个事件对象都处于已通知状态。尽管它原先发现 h A u t o R e s e t E v e n t 1处于已通知状态,但是现在它将该对象

20、视为未通知状态。前面讲过,有一个重要问题必须注意,即Wa i t F o r M u l t i p l e O b j e c t s是以原子操作方式运行的。当它检查内核对象的状态时,其他任何线程都无法背着对象改变它的状态。这可以防止出现死锁情况。试想,如果一个线程看到h A u t o R e s e t E v e n t 1已经得到通知并将事件重置为未通知状态,然后,另一个线程发现h A u t o R e s e t E v e n t 2已经得到通知并将该事件重置为未通知状态,那么这两个线程均将被冻结:一个线程将等待另一个线程已经得到的对象,另一个线程将等待该线程已经得到的对象。W

21、a i t F o r M u l t i p l e O b j e c t s能够确保这种情况永远不会发生。这会产生一个非常有趣的问题,即如果多个线程等待单个内核对象,那么当该对象变成已通知状态时,系统究竟决定唤醒哪个线程呢?M i c r o s o f t对这个问题的正式回答是:“算法是公平的。”M i c r o s o f t不想使用系统使用的内部算法。它只是说该算法是公平的,这意味着如果多194计计第二部分编程的具体方法下载个线程正在等待,那么每当对象变为已通知状态时,每个线程都应该得到它自己的被唤醒的机会。这意味着线程的优先级不起任何作用,即高优先级线程不一定得到该对象。这还意

22、味着等待时间最长的线程不一定得到该对象。同时得到对象的线程有可能反复循环,并且再次得到该对象。但是,这对于其他线程来说是不公平的,因此该算法将设法防止这种情况的出现。但是这不一定做得到。在实际操作中,M i c r o s o f t使用的算法是常用的“先进先出”的方案。等待了最长时间的线程将得到该对象。但是系统中将会执行一些操作,以便改变这个行为特性,使它不太容易预测。这就是为什么M i c r o s o f t没有明确说明该算法如何起作用的原因。操作之一是让线程暂停运行。如果一个线程等待一个对象,然后该线程暂停运行,那么系统就会忘记该线程正在等待该对象。这是一个特性,因为没有理由为一个暂

23、停运行的线程进行调度。当后来该线程恢复运行时,系统将认为该线程刚刚开始等待该对象。当调试一个进程时,只要到达一个断点,该进程中的所有线程均暂停运行。因此,调试一个进程会使“先进先出”的算法很难预测其结果,因为线程常常暂停运行,然后再恢复运行。9.3 事件内核对象在所有的内核对象中,事件内核对象是个最基本的对象。它们包含一个使用计数(与所有内核对象一样),一个用于指明该事件是个自动重置的事件还是一个人工重置的事件的布尔值,另一个用于指明该事件处于已通知状态还是未通知状态的布尔值。事件能够通知一个操作已经完成。有两种不同类型的事件对象。一种是人工重置的事件,另一种是自动重置的事件。当人工重置的事件

24、得到通知时,等待该事件的所有线程均变为可调度线程。当一个自动重置的事件得到通知时,等待该事件的线程中只有一个线程变为可调度线程。当一个线程执行初始化操作,然后通知另一个线程执行剩余的操作时,事件使用得最多。事件初始化为未通知状态,然后,当该线程完成它的初始化操作后,它就将事件设置为已通知状态。这时,一直在等待该事件的另一个线程发现该事件已经得到通知,因此它就变成可调度线程。这第二个线程知道第一个线程已经完成了它的操作。下面是C r e a t e E v e n t函数,用于创建事件内核对象:第3章已经介绍了内核对象的操作技巧,比如,如何设置它们的安全性,如何进行使用计数,如何继承它们的句柄,

25、如何按名字共享对象等。由于现在你对所有这些对象都已经熟悉了,所以不再介绍该函数的第一个和最后一个参数。F M a n n u a l R e s e t参数是个布尔值,它能够告诉系统是创建一个人工重置的事件(T R U E)还是创建一个自动重置的事件(FA L S E)。f I n i t i a l S t a t e参数用于指明该事件是要初始化为已通知状态(T R U E)还是未通知状态(FA L S E)。当系统创建事件对象后,c r e a t e E v e n t就将与进程相关的句柄返回给事件对象。其他进程中的线程可以获得对该对象的访问权,方法是使用在p s z N a m e参数

26、中传递的相同值,使用继承性,使用 D u p l i c a t e H a n d l e函数等来调用C r e a t e E v e n t,或者调用O p e n E v e n t,在p s z N a m e参数中设定一个与调用C r e a t e E v e n t时设定的名字相匹配的名字:第 9章 线程与内核对象的同步计计195下载与所有情况中一样,当不再需要事件内核对象时,应该调用C l o s e H a n d l e函数。一旦事件已经创建,就可以直接控制它的状态。当调用 S e t E v e n t时,可以将事件改为已通知状态:当调用R e s e t E v e

27、n t函数时,可以将该事件改为未通知状态:就是这么容易。M i c r o s o f t为自动重置的事件定义了应该成功等待的副作用规则,即当线程成功地等待到该对象时,自动重置的事件就会自动重置到未通知状态。这就是自动重置的事件如何获得它们的名字的方法。通常没有必要为自动重置的事件调用 R e s e t E v e n t函数,因为系统会自动对事件进行重置。但是,M i c r o s o f t没有为人工重置的事件定义成功等待的副作用。让我们观察一个简单的例子,以便说明如何使用事件内核对象对线程进行同步。下面就是这个代码:196计计第二部分编程的具体方法下载当这个进程启动时,它创建一个人工

28、重置的未通知状态的事件,并且将句柄保存在一个全局变量中。这使得该进程中的其他线程能够非常容易地访问同一个事件对象。现在 3个线程已经产生。这些线程要等待文件的内容读入内存,然后每个线程都要访问它的数据。一个线程进行单词计数,另一个线程运行拼写检查器,第三个线程运行语法检查器。这 3个线程函数的代码的开始部分都相同,每个函数都调用 Wa i t F o r S i n g l e O b j e c t,这将使线程暂停运行,直到文件的内容由主线程读入内存为止。一旦主线程将数据准备好,它就调用 S e t E v e n t,给事件发出通知信号。这时,系统就使所有这3个辅助线程进入可调度状态,它们

29、都获得了C P U时间,并且可以访问内存块。注意,这3个线程都以只读方式访问内存。这就是所有 3个线程能够同时运行的唯一原因。还要注意,如何计算机上配有多个C P U,那么所有3个线程都能够真正地同时运行,从而可以在很短的时间内完成大量的操作。如果你使用自动重置的事件而不是人工重置的事件,那么应用程序的行为特性就有很大的差别。当主线程调用S e t E v e n t之后,系统只允许一个辅助线程变成可调度状态。同样,也无法保证系统将使哪个线程变为可调度状态。其余两个辅助线程将继续等待。已经变为可调度状态的线程拥有对内存块的独占访问权。让我们重新编写线程的函数,使得每个函数在返回前调用S e t

30、 E v e n t函数(就像Wi n M a i n函数所做的那样)。这些线程函数现在变成下面的形式:第 9章 线程与内核对象的同步计计197下载当线程完成它对数据的专门传递时,它就调用 S e t E v e n t函数,该函数允许系统使得两个正在等待的线程中的一个成为可调度线程。同样,我们不知道系统将选择哪个线程作为可调度线程,但是该线程将进行它自己的对内存块的专门传递。当该线程完成操作时,它也将调用S e t E v e n t函数,使第三个即最后一个线程进行它自己的对内存块的传递。注意,当使用自动重置事件时,如果每个辅助线程均以读/写方式访问内存块,那么就不会产生任何问题,这些线程将

31、不再被要求将数据视为只读数据。这个例子清楚地展示出使用人工重置事件与自动重置事件之间的差别。为了完整起见,下面再介绍一个可以用于事件的函数:P u l s e E v e n t函数使得事件变为已通知状态,然后立即又变为未通知状态,这就像在调用S e t E v e n t后又立即调用R e s e t E v e n t函数一样。如果在人工重置的事件上调用P u l s e E v e n t函数,那么在发出该事件时,等待该事件的任何一个线程或所有线程将变为可调度线程。如果在自动重置事件上调用P u l s e E v e n t函数,那么只有一个等待该事件的线程变为可调度线程。如果在发出事

32、件时没有任何线程在等待该事件,那么将不起任何作用。P u l s e E v e n t函数并不非常有用。实际上我在自己的应用程序中从未使用它,因为根本不知道什么线程将会看到事件的发出并变成可调度线程。由于在调用 P u l s e E v e n t时无法知道任何线程的状态,因此该函数并不那么有用。我相信在有些情况下,虽然 P u l s e E v e n t函数可以方便地供你使用,但是你根本想不起要去使用它。关于 P u l s e E v e n t函数的比较详细的说明,请参见本章后面对S i n g l e O b j e c t A n d Wa i t函数的介绍。H a n d

33、s h a k e示例应用程序清单9-1中列出了H a n d s h a k e(“09 Handshake.exe”)应用程序,它展示了自动重置事件的使用情况。该应用程序的源代码文件和资源文件均在本书所附光盘上的 0 9-H a n d s h a k e目录下。当运行H a n d s h a k e应用程序时,就会出现图9-3所示的对话框。H a n d s h a k e应用程序接受一个请求字符串,再将该字符串中的所有字符反转,然后将结果放入R e s u l t域。H a n d s h a k e应用程序的出色之处在于它完成这个重要任务时所用的方法不同一般。H a n d s h

34、 a k e能够解决常见的编程问题。现在有一个客户机和一个服务器,它们之间需要互198计计第二部分编程的具体方法下载相进行通信。开始时,服务器无事可做,因此它进入等待状态。当客户机准备将一个请求提交给服务器时,它将该请求放入一个共享内存缓冲区中,然后发出一个事件通知,这样,服务器线程就会知道查看数据缓冲区并处理客户机的请求。当服务器线程忙于处理该请求的时候,客户机的线程必须进入等待状态,直到服务器准备好请求的结果为止。因此客户机进入等待状态,直到服务器发出另一个事件通知,指明结果已经准备好,可供客户机进行处理。当客户机再次被唤醒的时候,它就知道结果已经放入共享数据缓冲区中,并且可以将结果显示给

35、用户。图9-3 Handshake 对话框当该应用程序启动运行时,它立即创建两个未通知的自动重置的事件对象。一个事件是 g _h e v t R e q u e s t S u b m i t t e d,用于指明何时为服务器准备一个请求。该事件由服务器线程等待,并由客户机线程发出通知。第二个事件是g _ h e v t R e s u l t R e t u r n e d,用来指明何时为客户机准备好结果。客户机线程等待该事件,而服务器线程则负责发出该事件的通知。当各个事件创建后,服务器线程就产生并且执行 S e r v e r T h r e a d函数。该函数立即让服务器等待客户机的请求

36、。与此同时,主线程(它也是客户机线程)调用 D i a l o g B o x函数,该函数负责显示应用程序的用户界面。可以将一些文字输入R e q u e s t域,然后,当点击Submit Request ToS e r v e r(将请求提交给服务器)时,请求字符串将被放入由客户机和服务器线程共享的一个缓冲区,并发出g _ h e v t R e q u e s t S u b m i t t e d事件的通知。然后客户机线程通过等待g _ h e v t R e s u l t R e t u r e n e d事件来等待服务器的结果。服务器醒来,将共享内存缓冲区中的字符串反转,然后发出

37、 g _ h e v t R e s u l t R e t u r n e d事件的通知。服务器的线程循环运行,以便等待客户机的另一个请求。注意,该应用程序决不会调用R e s e t E v e n t函数,因为没有必要。自动重置的事件在等待成功后会自动恢复未通知状态。与此同时,客户机线程发现g _ h e v t R e s u l t R e t u r n e d事件已经变为已通知状态。它醒来,并将字符串从共享内存缓冲区拷贝到用户界面的R e s u l t域。也许这个应用程序剩下的唯一的一个值得注意的特性是它如何关闭。若要关闭该应用程序,只需要关闭它的对话框。这会导致调用的_ t

38、Wi n M a i n中的D i a l o g B o x函数返回。这时,主线程将一个特殊字符串拷贝到共享缓冲区,并唤醒服务器的线程,以便处理该特殊请求。主线程等待服务器线程确认请求已经收到,并等待服务器线程终止运行。当服务器线程发现该特殊的客户机请求字符串时,它就退出循环,而该线程则终止运行。我选择让主线程等待服务器线程终止运行,方法是调用Wa i t F o r M u l t i p l e O b j e c t s函数,这样,就可以看到该函数是如何使用的。实际上,也可以调用 Wa i t F o r S i n g l e O b j e c t函数,传递服务器线程的句柄,一切将

39、以完全相同的方式来运行。一旦主线程知道服务器线程已经停止运行后,我将 3次调用C l o s e H a n d l e函数,以便正确地撤消应用程序正在使用的所有内核对象。当然,系统能够自动执行这项操作,但是如果我自己进行操作,我的感觉会更好些。我喜欢能够随时控制我的代码。第 9章 线程与内核对象的同步计计199下载清单9-1 Handshake示例应用程序200计计第二部分编程的具体方法下载第 9章 线程与内核对象的同步计计201下载202计计第二部分编程的具体方法下载第 9章 线程与内核对象的同步计计203下载9.4 等待定时器内核对象等待定时器是在某个时间或按规定的间隔时间发出自己的信号

40、通知的内核对象。它们通常用来在某个时间执行某个操作。若要创建等待定时器,只需要调用C r e a t e Wa i t a b l e Ti m e r函数:p s a和p s z N a m e这两个参数在第3章中做过介绍。当然,进程可以获得它自己的与进程相关的现有等待定时器的句柄,方法是调用O p e n Wa i t a b l e Ti m e r函数:204计计第二部分编程的具体方法下载与事件的情况一样,f M a n u a l R e s e t参数用于指明人工重置的定时器或自动重置的定时器。当发出人工重置的定时器信号通知时,等待该定时器的所有线程均变为可调度线程。当发出自动重置

41、的定时器信号通知时,只有一个等待的线程变为可调度线程。等待定时器对象总是在未通知状态中创建。必须调用 S e t Wa i t a b l e Ti m e r函数来告诉定时器你想在何时让它成为已通知状态:这个函数带有若干个参数,使用时很容易搞混。显然,h Ti m e r参数用于指明你要设置的定时器。p D u e Ti m e和l P e r i o d两个参数是一道使用的。P D u e Ti m e r参数用于指明定时器何时应该第一次报时,而l P e r i o d参数则用于指明此后定时器应该间隔多长时间报时一次。下面的代码用于将定时器的第一次报时的时间设置在2 0 0 2年1月1日

42、的下午1点钟,然后每隔6小时报时一次:代码首先对S Y S T E M T I M E结构进行初始化,该结构用于指明定时器何时第一次报时(发出信号通知)。我将该时间设置为本地时间,即计算机所在时区的正确时间。S e t Wa i t a b l e Ti m e r的第 9章 线程与内核对象的同步计计205下载第二个参数的原型是个常量LARGE_INTEGER*,因此它不能直接接受S Y S T E M T I M E结构。但是,F I L E T I M E结构和L A R G E _ I N T E G E R结构拥有相同的二进制格式,都包含两个 3 2位的值。因此,我们可以将S Y S

43、T E M T I M E结构转换成F I L E T I M E结构。再一个问题是,S e t Wa i t a b l e Ti m e r希望传递给它的时间始终都采用世界协调时(U T C)的时间。调用L o c a l F i l e Ti m e To F i l e Ti m e函数,就可以很容易地进行时间的转换。由于F I L E T I M E和L A R G E _ I N T E G E R结构具有相同的二进制格式,因此可以像下面这样将F I L E T I M E结构的地址直接传递给S e t Wa i t a b l e Ti m e r:实际上,这是我最初的做法。但是

44、这是个大错误。虽然 F I L E T I M E和L A R G E _ I N T E G E R结构采用相同的二进制格式,但是这两个结构的调整要求不同。所有 F I L E T I M E结构的地址必须从一个3 2位的边界开始,而所有 L A R G E _ I N T E G E R结构的地址则必须从 6 4位的边界开始。调用S e t Wa i t a b l e Ti m e r函数和给它传递一个 F I L E T I M E结构时是否能够正确地运行,取决于F I L E T I M E结构是否恰好位于6 4位的边界上。但是,编译器能够确保L A R G E _ I N T E

45、G E R结构总是从6 4位的边界开始,因此要进行的正确操作(也就是所有时间都能保证起作用的操作)是将F I L E T I M E的成员拷贝到L A R G E _ I N T E G E R的成员中,然后将L A R G E _ I N T E G E R的地址传递给S e t Wa i t a b l e Ti m e r。注意x 8 6处理器能够悄悄地处理未对齐的数据引用。因此当应用程序在 x86 CPU上运行时,将F I L E T I M E的地址传递给S e t Wa i t a b l r Ti m e r总是可行的。但是,其他处理器,如A l p h a处理器,则无法像x 8

46、 6处理器那样悄悄地处理未对齐的数据引用。实际上,大多数其他处理器都会产生一个 E X C E P T I O N _ D ATAT Y P E _ M I S A L I G N M E N T异常,它会导致进程终止运行。当你将 x 8 6计算机上运行的代码移植到其他处理器时,产生问题的最大原因是出现了对齐错误。如果现在注意对齐方面的问题,就能够在以后省去几个月的代码移植工作。关于对齐问题的详细说明,参见第 1 3章。现在,若要使定时器在2 0 0 2年1月1日下午1点之后每隔6 h进行一次报时,我们应该将注意力转向l P e r i o d参数。该参数用于指明定时器在初次报时后每隔多长时间

47、(以毫秒为单位)进行一次报时。如果是每隔6 h进行一次报时,那么我传递 21 600 000(6 h每小时6 0 m i n每分钟6 0 s每秒1 0 0 0 m s)。另外,如果给它传递了以前的一个绝对时间,比如1 9 7 5年1月1日下午1点,那么S e t Wa i t a b l e Ti m e r的运行就不会失败。如果不设置定时器应该第一次报时的绝对时间,也可以让定时器在一个相对于调用S e t Wa i t a b l e Ti m e r的时间进行报时。只需要在p D u e Ti m e参数中传递一个负值。传递的值必须是以1 0 0 n s为间隔。由于我们通常并不以 1 0

48、0 n s的间隔来思考问题,因此我们要说明一下 1 0 0 n s的具体概念:1 s=1 0 0 0 m s=1 0 0 0 0 0 0 s=1 0 0 0 0 0 0 0 0 0 n s。下面的代码用于将定时器设置为在调用S e t Wa i t a b l e Ti m e r函数后5 s第一次报时:206计计第二部分编程的具体方法下载通常情况下,你可能想要一个一次报时的定时器,它只是发出一次报时信号,此后再也不发出报时信号。若要做到这一点,只需要为 l P e r i o d参数传递0即可。然后可以调用C l o s e H a n d l e函数,关闭定时器,或者再次调用 S e t

49、Wa i t a b l e Ti m e r函数,重新设置时间,为它规定一个需要遵循的新条件。S e t Wa i t a b l e Ti m e r的最后一个参数是f R e s u m e,它可以用于支持暂停和恢复的计算机。通常可以为该参数传递FA L S E,就像我在上面这个代码段中设置的那样。但是,如果你编写了一个会议安排类型的应用程序,在这个应用程序在中,你想设置一个为用户提醒会议时间安排的定时器,那么应该传递T R U E。当定时器报时的时候,它将使计算机摆脱暂停方式(如果它处于暂停状态的话),并唤醒等待定时器报时的线程。然后该应用程序运行一个波形文件,并显示一个消息框,告诉用

50、户即将举行的会议。如果为 f R e s u m e参数传递FA L S E,定时器对象就变为已通知状态,但是它唤醒的线程必须等到计算机恢复运行(通常由用户将它唤醒)之后才能得到C P U时间。除了上面介绍的定时器函数外,最后还有一个C a n c e l Wa i t a b l e Ti m e r函数:这个简单的函数用于取出定时器的句柄并将它撤消,这样,除非接着调用 S e t Wa i t a b l e Ti m e r函数以便重新设置定时器,否则定时器决不会进行报时。如果想要改变定时器的报时条件,不必在调用S e t Wa i t a b l e Ti m e r函数之前调用C a

展开阅读全文
相关资源
相关搜索

当前位置:首页 > 技术资料 > 其他杂项

本站为文档C TO C交易模式,本站只提供存储空间、用户上传的文档直接被用户下载,本站只是中间服务平台,本站所有文档下载所得的收益归上传人(含作者)所有。本站仅对用户上传内容的表现方式做保护处理,对上载内容本身不做任何修改或编辑。若文档所含内容侵犯了您的版权或隐私,请立即通知淘文阁网,我们立即给予删除!客服QQ:136780468 微信:18945177775 电话:18904686070

工信部备案号:黑ICP备15003705号© 2020-2023 www.taowenge.com 淘文阁