码迷,mamicode.com
首页 > 编程语言 > 详细

Windows API学习---线程与内核对象的同步

时间:2015-12-23 12:43:13      阅读:282      评论:0      收藏:0      [点我收藏+]

标签:

前言

  若干种内核对象,包括进程,线程和作业。可以将所有这些内核对象用于同步目的。对于线程同步来说,这些内核对象中的每种对象都可以说是处于已通知或未通知的状态之中。这种状态的切换是由Microsoft为每个对象建立的一套规则来决定的。例如,进程内核对象总是在未通知状态中创建的。当进程终止运行时,操作系统自动使该进程的内核对象处于已通知状态。一旦进程内核对象得到通知,它将永远保持这种状态,它的状态永远不会改为未通知状态。
  当进程正在运行的时候,进程内核对象处于未通知状态,当进程终止运行的时候,它就变为已通知状态。进程内核对象中是个布尔值,当对象创建时,该值被初始化为FALSE(未通知状态)。当进程终止运行时,操作系统自动将对应的对象布尔值改为TRUE,表示该对象已经得到通知。
  如果编写的代码是用于检查进程是否仍在运行,那么只需要调用一个函数,让操作系统去检查进程对象的布尔值,这非常简单。你也可能想要告诉系统使线程进入等待状态,然后当布尔值从FALSE为TRUE时自动唤醒该线程。这样,你可以编写一个代码,在这个代码中,需要等待子进程终止运行的父进程中的线程只需要使自己进入睡眠状态,直到标识子进程的内核对象变为已通知状态即可。你将会看到,Microsoft的Windows提供了一些能够非常容易地完成这些操作的函数。
  刚才讲了Microsoft为进程内核对象定义了一些规则。实际上,线程内核对象也遵循同样的规则。即线程内核对象总是在未通知状态中创建。当线程终止运行时,操作系统会自动将线程对象的状态改为已通知状态。因此,可以将相同的方法用于应用程序,以确定线程是否不再运行。与进程内核对象一样,线程内核对象也可以处于已通知状态或未通知状态。下面的内核对象可以处于已通知状态或未通知状态:
  ■ 进程    ■ 文件修改通知
  ■ 线程    ■ 事件
  ■ 作业    ■ 可等待定时器

  ■ 文件    ■ 信标
  ■ 控制台输入 ■ 互斥对象

  线程可以使自己进入等待状态,直到一个对象变为已通知状态。注意,用于控制每个对象的已通知/未通知状态的规则要根据对象的类型而定。前面已经提到进程和线程对象的规则及作业的规则。
  本章将要介绍允许线程等待某个内核对象变为已通知状态所用的函数。然后我们将要讲述Wi n d o w s提供的专门用来帮助实现线程同步的各种内核对象、如事件、等待计数器,信标和互斥对象。
  当我最初开始学习这项内容时,我设想内核对象包含了一面旗帜(在空中飘扬的旗帜,不是耷拉下来的旗帜),这对我很有帮助。当内核对象得到通知时,旗帜升起来;当对象未得到通知时,旗帜就降下来(见图9 - 1)。
  当线程等待的对象处于未通知状态(旗帜降下)中时,这些线程不可调度。但是一旦对象变为已通知状态(旗帜升起),线程看到该标志变为可调度状态,并且很快恢复运行(见图9 - 2)。

  技术分享技术分享技术分享技术分享

  技术分享

1  等待函数

  等待函数可使线程自愿进入等待状态,直到一个特定的内核对象变为已通知状态为止。这些等待函数中最常用的是WaitForSingleObject:

DWORD WaitForSingleObject(
    HANDLE hObject,
    DWORD dwMilliseconds);

  当线程调用该函数时,第一个参数hObject标识一个能够支持被通知/未通知的内核对象(前面列出的任何一种对象都适用)。第二个参数dwMilliseconds允许该线程指明,为了等待该对象变为已通知状态,它将等待多长时间。
  调用下面这个函数将告诉系统,调用函数准备等待到hProcess句柄标识的进程终止运行为止:

WaitForSingleObject(hProcess, INFINITE);

  第二个参数告诉系统,调用线程愿意永远等待下去(无限时间量),直到该进程终止运行。

  通常情况下, INFINITE是作为第二个参数传递给WaitForSingleObject的,不过也可以传递任何一个值(以毫秒计算)。顺便说一下,INFINITE已经定义为0xFFFFFFFF(或-1)。当然,传递INFINITE有些危险。如果对象永远不变为已通知状态,那么调用线程永远不会被唤醒,它将永远处于死锁状态,不过,它不会浪费宝贵的CPU时间。下面是如何用一个超时值而不是INFINITE来调用WaitForSingleObject的例子:

DWORD dw = WaitForSingleObject(hProcess, 5000);
switch(dw){
    case WAIT_OBJECT_0:
        // The process terminated
        break;
    case WAIT_TIMEOUT:
        // The process did not terminate within 5000 milliseconds
        break;
    case WAIT_FAILED:
        // Bad call to function (invalid handle?)
        break;
}

  上面这个代码告诉系统,在特定的进程终止运行之前,或者在5000ms时间结束之前,调用线程不应该变为可调度状态。因此,如果进程终止运行,那么这个函数调用将在不到5000ms的时间内返回,如果进程尚未终止运行,那么它在大约5000ms时间内返回。注意,不能为dwMilliseconds传递0。如果传递了0,WaitForSingleObject函数将总是立即返回。
  WaitForSingleObject的返回值能够指明调用线程为什么再次变为可调度状态。如果线程等待的对象变为已通知状态,那么返回值是WAIT_OBJECT_0。如果设置的超时已经到期,则返回值是WAIT_TIMEOUT。如果将一个错误的值(如一个无效句柄)传递给WaitForSingleObject,那么返回值将是WAIT_FAILED(若要了解详细信息,可调用GetLastError)。
  下面这个函数WaitForMultipleObjects与WaitForSingleObject函数很相似,区别在于它允许调用线程同时查看若干个内核对象的已通知状态:

DWORD WaitForMultipleObjects(
    DWORD dwCount,
    CONST HANDLE* phObjects,
    BOOL fWaitAll,
    DWORD dwMilliseconds);

  dwCounts参数用于指明想要让函数查看的内核对象的数量。这个值必须在1与MAXIMUM_WAITOBJECTS(在Windows头文件中定义为64)之间。phObjects参数是指向内核对象句柄的数组的指针。

  可以以两种不同的方式来使用WaitForMultipleObjects函数。一种方式是让线程进入等待状态,直到指定内核对象中的任何一个变为已通知状态。另一种方式是让线程进入等待状态,直到所有指定的内核对象都变为已通知状态。fWaitAll参数告诉该函数,你想要让它使用何种方式。如果为该参数传递TRUE,那么在所有对象变为已通知状态之前,该函数将不允许调用线程运行。

  dwMilliseconds参数的作用与它在Wa WaitForSingleObject中的作用完全相同。如果在等待的时候规定的时间到了,那么该函数无论如何都会返回。同样,通常为该参数传递INFINITE,但是在编写代码时应该小心,以避免出现死锁情况。
  WaitForMultipleObjects函数的返回值告诉调用线程,为什么它会被重新调度。可能的返回值是WAIT_FAILED和WAIT_TIMEOUT,这两个值的作用是很清楚的。如果为fWaitAll参数传递TRUE,同时所有对象均变为已通知状态,那么返回值是WAIT_OBJECT_0。如果为fWaitAll传递FALSE,那么一旦任何一个对象变为已通知状态,该函数便返回。在这种情况下,你可能想要知道哪个对象变为已通知状态。返回值是WAIT_OBJECT_0与(WAIT_OBJECT_0 + dwCount - 1)之间的一个值。换句话说,如果返回值不是WAIT_TIMEOUT,也不是WAIT_FAILED,那么应该从返回值中减去WAIT_OBJECT_0。产生的数字是作为第二个参数传递给WaitForMultipleObjects的句柄数组中的索引。该索引说明哪个对象变为已通知状态。下面是说明这一情况的一些示例代码:

HANDLE h[3];
h[0] = hProcess1;
h[1] = hProcess2;
h[2] = hProcess3;

DWORD dw = WaitForMultipleObjects(3, h, FALSE, 5000);
switch(dw){
    case WAIT_FAILED:
        // Bad call to function (invalid handle?)
        break;
    case WAIT_TIMEOUT:
        // None of the objects became signaled within 5000 milliseconds
        break;
    case WAIT_OBJECT_0 + 0:
        // The process identified by h[0](hProcess1)  terminated
        break;
    case WAIT_OBJECT_0 + 1:
        // The process identified by h[0](hProcess2)  terminated
        break;
    case WAIT_OBJECT_0 + 2:
        // The process identified by h[0](hProcess3)  terminated
        break;
}

  如果为fWaitAll参数传递FALSE,WaitForMultipleObjects就从索引0开始向上对句柄数组进行扫描,同时已通知的第一个对象终止等待状态。这可能产生一些你不希望有的结果。例如,通过将3个进程句柄传递给该函数,你的线程就会等待3个子进程终止运行。如果数组中索引为0的进程终止运行,WaitForMultipleObjects就会返回。这时该线程就可以做它需要的任何事情,然后循环反复,等待另一个进程终止运行。如果该线程传递相同的3个句柄,该函数立即再次返回WAIT_OBJECT_0。除非删除已经收到通知的句柄,否则代码就无法正确地运行。

2  成功等待的副作用

  对于有些内核对象来说,成功地调用WaitForSingleObject和WaitForMultipleObjects,实际上会改变对象的状态。成功地调用是指函数发现对象已经得到通知并且返回一个相对于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返回给调用线程。但是就在函数返回之前,该事件将被置为未通知状态,这就是成功等待的副作用。
  这个副作用将用于自动清除内核对象,因为它是M i c r o s o f t为这种类型的对象定义的规则之一。其他对象拥有不同的副作用,而有些对象则根本没有任何副作用。进程和线程内核对象就根本没有任何副作用,也就是说,在这些对象之一上进行等待决不会改变对象的状态。由于本章要介绍各种不同的内核对象,因此我们将要详细说明它们的成功等待的副作用。
  究竟是什么原因使得WaitForMultipleObjects函数如此有用呢,因为它能够以原子操作方式来执行它的所有操作。当一个线程调用WaitForMultipleObjects函数时,该函数能够测试所有对象的通知状态,并且能够将所有必要的副作用作为一项操作来执行。
  让我们观察一个例子。两个线程以完全相同的方式来调用WaitForMultipleObjects:

 

Windows API学习---线程与内核对象的同步

标签:

原文地址:http://www.cnblogs.com/MrYuan/p/5069351.html

(0)
(0)
   
举报
评论 一句话评论(0
登录后才能评论!
© 2014 mamicode.com 版权所有  联系我们:gaon5@hotmail.com
迷上了代码!