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

《windows核心编程学习笔记》——使用互斥量变量内核对象进行线程同步

2011-09-08 20:27 465 查看
用途:互斥量内核对象用来确保一个线程独占对一个资源的访问。

用法:

如多个线程需要对同一内存进行读写操作。大致操作如下:

HANDLE hMutex = CreateMutex(NULL, FALSE, NULL); //创建一个互斥量

T Read()

{

WaitForSingleObject(hMutex, INFINITE);

//read the buffer

ReleaseMutex(hMutex);

}

void Write(T data)

{

WaitForSingleObject(hMutex, INFINITE);

//write the buffer

ReleaseMutex();

}

理解:

创建一个互斥量用于线程同步,互斥量对象包含一个使用计数,线程ID及一个递归计数。线程ID用来标识当前占用这个互斥量的是系统的哪个线程,递归计数标识这个线程占用改互斥量的次数。CreateMutex的第二个参数指定了互斥量内核对象初始创建时的状态(TRUE 未触发,FALSE已触发),其他参数参见MSDN。假设当前有两个线程,一个进行操作,一个进行读操作。

假设写线程第一次执行Write操作,调用等待函数WaitForSingleObject, 传入互斥量句柄。在内部,等待函数会检查互斥量线程ID是否为0(互斥量处于触发状态)。如果为0,那么函数会把线程ID设为条用线程的线程ID,把递归计数设为1,然后让调用线程继续运行。需要说明的是WaitForSingleObject 函数使一个线程自愿进入等待状态,直到指定的内核对象被触发为止。但是,如果线程在调用一个等待函数的时候,相应的内核对象已经处于触发状态,那么线程是不会进入等待状态的。也就是说等待函数等待的是内核对象状态瞬间的变化(由未触发变为已触发),而不是等待变化完的状态。如果上面的例子中创建互斥量的时候,第二个参数传入的是TRUE,也就是初始状态是未触发的,那么除非我们明确的调用函数使内核状态变为触发状态,否则读写线程永远也不会捕捉到内核状态的变化,这样就会出现永远等待的情况。

回到刚才的情况,写线程正在执行写操作,互斥量内核对象的线程ID记录这当前占有这个互斥量的线程ID。此时,读线程执行Read操作,调用等待函数,等待函数检查互斥量线程ID,发现ID不为0,读线程进入等待状态。 当写线程完成写操作之后,调用ReleaseMutex。 在内部,这个函数会将互斥量内核对象的递归计数减1,当递归计数变成0的时候,函数还会将线程ID设为0,这样就触发了内核对象。当对象被触发的时候,系统会检查有没有其他的线程正在等待该互斥量,如果有系统会“公平地”选择一个正在等待的线程,把互斥量的所有权给它,同样,内核对象把线程ID设为占有互斥量的那个线程ID,递归计数设为1。上例中,此时的读线程就有机会读内存了。如果没有线程等待,那么互斥量会保持在触发状态,这样下一个等待它的线程就可以立即进入互斥区域了。

注意:

(1)假设线程试图等待一个未触发的内核对象,线程通常会进入等待状态。但对于互斥量内核对象却不一定如此,如果此时线程ID和互斥量内部线程ID一致,那么线程会保持可调度状态,即使互斥量处于未触发状态。简单点说就是在当前线程拥有互斥区的时候又调用WaitForSingleObject。这也是是递归计数大于1的唯一途径。如果多次成功等待了互斥量,也必须调用相应次数的ReleaseMutex函数使互斥量处于触发状态。

(2)线程是否互斥量的时候,线程ID如果与互斥量内部ID一致,递归计数减1。如果不一致,则ReleaseMutex会返回FALSE给调用者,指示调用失败。如果释放互斥量的线程已经终止,系统会认为互斥量已被“遗弃”。这是系统会自动将互斥量线程ID设为0,递归计数设为0,互斥量处于已触发状态。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: