您的位置:首页 > 编程语言 > C语言/C++

《Windows via C/C++》学习笔记 —— 内核对象的“线程同步”之“互斥内核对象”

2008-08-14 17:47 381 查看
  互斥内核对象确保一个线程独占地访问资源。

  互斥内核对象的行为特征和关键代码段有点类似,但是它是属于内核对象,而关键代码段是用户模式对象,这导致了互斥内核对象的运行速度比关键代码段要低。所以,在考虑线程同步问题的时候,首先考虑用户模式的对象。

  但是,互斥内核对象可以跨进程使用,当需要实现多进程之间的线程同步,就可用考虑使用互斥内核对象。而这点,关键代码段无能为力。

  在互斥内核对象内部,有以下一些重要的数据:

1、使用计数:表明该互斥内核对象被打开的次数。

2、线程ID

3、递归计数器

  线程ID表明了该互斥内核对象被哪个线程所拥有,递归计数器表明了这个线程(拥有互斥对象)拥有这个互斥对象的次数。

  互斥对象的使用规则如下

如果内部线程ID为0(或者是一个无效的线程ID),该互斥内核对象不被任何线程所拥有,会发出通知信号,即处于“已通知”状态。

如果线程ID不为0,而是一个有效的线程ID,那么该互斥内核对象就被这个线程所拥有,而且该互斥内核对象为“未通知”状态。

与其他内核对象不同的是,互斥内核对象在操作系统中有着特殊的代码,允许以不正常的规则进行使用。

  要使用互斥内核对象,首先必须创建它:

HANDLE CreateMutex(

PSECURITY_ATTRIBUTES psa, //安全属性

BOOL bInitialOwner, //互斥对象是否开始就被调用该函数的线程所拥有

PCTSTR pszName); //该互斥内核对象的名字

  Windows Vista中还提供了一个函数用于创建一个互斥内核对象:

HANDLE CreateMutexEx(

PSECURITY_ATTRIBUTES psa, //安全属性

PCTSTR pszName, //该互斥内核对象的名字

DWORD dwFlags, //互斥对象是否开始就被调用该函数的线程所拥有

DWORD dwDesiredAccess); //访问限制

  第1个函数中的bInitialOwner参数如果为TRUE,则创建的互斥内核对象一开始就被调用这个函数的线程所拥有,它的线程ID被设置为该线程的ID,递归计数器被设置为1。

  如果传递FALSE给这个参数,则互斥内核对象的线程ID和递归计数器被设置为0,表明该互斥内核对象不被任何线程所拥有,该互斥内核对象处于“已通知”状态。

  第2个函数的dwFlags的意义和第1个函数的bInitialOwner参数其实是一样的,0就好比FALSE,CREATE_MUTEX_INITIAL_OWNER就相当于TURE。

  这两个函数成功,返回互斥内核对象的句柄,失败返回NULL。

  你通过“名字”来可以打开一个已经创建了的互斥内核对象:

HANDLE OpenMutex(

DWORD dwDesiredAccess, //访问限制

BOOL bInheritHandle, //是否允许返回的句柄被子进程继承

PCTSTR pszName); //名字

  创建了一个互斥内核对象,得到了它的句柄之后,就可以让它保护资源了。

  一个线程中(下面用T表示),在你需要访问资源之前,可以先调用“等待函数”,传递该互斥对象(下面用M表示)的句柄该这些等待函数,在等待函数内部,通过句柄查看M的线程ID,如果不为0,表明M处于“未通知”状态,线程T进入等待状态(有例外,下面会讲)。此时系统会记住这个情况,当M被其他线程释放,它的线程ID重新被设置为0的时候,系统会将一个等待在它上面的线程(比如T)的ID设置为M的线程ID,同时将M的递归计数器设置为1,允许该线程(比如T)进入可调度状态。

  注意,对于互斥对象的线程ID的比较和设置都是以“原子”的形式进行的,所以互斥内核对象是“线程安全”的。

  下面来讲那个例外的情况,这就是互斥内核对象允许以不正常的规则进行使用。也就是在一个互斥内核对象处于“未通知”状态的时候,一个等待在它上面的线程“或许”可以继续运行。

  比如当前有一个处于“未通知状态”的互斥内核对象M,一个线程T(ID为X)。T调用等待函数等待M,这种情况下,通常T会进入等待状态。但是,系统查看T的ID和M的线程ID相同,都是X的情况下,线程并不会进入等待状态,而是保持在可调度状态。在线程成功等待互斥内核对象之后,互斥内核对象M的递归计数器加1。

  也就是说,一个互斥内核对象的递归计数器要大于1,就要让线程多次等待相同的互斥内核对象。

  一旦当前线程成功地等待到了一个互斥内核对象之后,该线程就可以独占某些资源,从而可以访问这些共享的资源了。试图访问这些资源的其他线程通过等待相同的互斥对象,就会进入等待状态之中。

  当前线程如果对资源访问结束,必须释放互斥内核对象,使用ReleaseMutex函数:

BOOL ReleaseMutex(HANDLE hMutex); //参数是互斥内核对象句柄

  该函数将互斥内核对象的递归计数减1。如果一个线程多次成功地等待一个互斥内核对象,就要同样以相同的次数调用ReleaseMutex函数,从而递减其递归计数,当互斥内核对象的递归计数减为0后,其线程ID被设置为0,进入“已通知”状态。

  当这个互斥内核对象进入“已通知”状态之时,系统查看当前是否有线程等待它,如果有,就以公平的原则选择其中一个线程,将这个互斥内核对象的线程ID设置为这个选中的线程的线程ID,互斥对象的递归计数被设置为1。

  综合上面所叙述的,可以总结出,互斥内核对象不同于其他内核对象,就是它有一个“线程所有权”的概念,这就使得互斥内核对象比较特殊。

  一个线程调用ReleaseMutex函数释放一个互斥对象,这时系统查看互斥对象的线程ID和这个线程的线程ID是否相同,如果相同,互斥对象的递归计数减1;否则ReleaseMutex不做任何工作,返回FALSE。

  还有一种现象,称做“互斥对象被抛弃”。

  假设一个互斥内核对象为一个线程所拥有,而这个线程却因为某些特殊的原因在终止,比如调用了ExitThread或TerminateThread函数,但是它在终止之前没有释放这个互斥对象。这个时候,系统能够跟踪拥有互斥内核对象的线程内核对象,系统知道这个互斥对象被一个线程抛弃了,就将互斥对象的线程ID设置为0, 将其递归计数设置为0。然后,系统查看是否有其他线程在等待这个互斥对象,如果有,就公平地选中一个,将互斥对象的线程ID设置为选中的线程的线程ID,这和前面的论述是一样的,差别是等待函数返回的值是WAIT_ABANDONED,而不是WAIT_OBJECT_0。这个时候,访问资源是不合适的,因为不知道资源处于何种状态。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: