第九章:用内核对象进行线程同步(二) .

来源:互联网 发布:下载解压软件 编辑:程序博客网 时间:2024/05/30 04:29
 

7 互斥量内核对象用来确保一个线程独占对一个资源的访问.

■ 组成:

①使用计数

②线程ID:标示当前占用这个互斥量的是系统中那个线程

③递归计数: 表示这个线程占用该互斥量的次数.

■ 与关键代码段的区别:

● 互斥量是内核模式下的,而关键代码段是用户模式下的.也意味着关键代码段比 互斥量要快.

● 不同进程中的线程可以访问同一个互斥量.

■ 互斥量的规则:

● 如果线程ID为0(即无效线程ID),那么该互斥量不为任何线程锁占有,它处于触 发状态

● 如果线程ID为非0,那么有一个线程已经占用了该互斥量,它处于未触发状态

● 与所有其他内核对象不同,OS对互斥量进行了特殊处理,欲行它们违反一些常 规的规则.

■ 互斥量的一些操作

● 创建

HANDLE CreateMutex(

PSECUTRY_ATTRIBUTES psa,

BOOL bInitialOwner,//互斥量的初始状态(TRUE表示处于未触发状态)线程ID //和递归计数分别设为当前线程ID和1

PCTSTR pszName);

HANDLE CreateMutexEx(

PSECUTRY_ATTRIBUTES psa,

PCTSTR pszName,

DWORD dwFlags,//相当于bInitialOwner(0表示 //FALSE,CREATE_MUTEX_INITIAL_OWNER等价于TRUE

DWORD dwDesiredAccess);

● 获取互斥量句柄

HANDLE OpenMutex(

DWORD dwDesiredAccess,

BOOL bInheritHandle,

PCTSTR pszName);

● 释放互斥量,使其处理可触发状态.

BOOL ReleaseMutex(HANDLE hMutex);

函数工作方式:使得对象的递归计数减一.如果线程成功地等待了互斥量对象不止一次,那么线程必须调用相同次数ReleaseMutex使得递归计数为0,并且此时线程ID也设为0.这样就出发了对象.

互斥量与其他内核对象的区别:

加入线程试图等待一个未触发的互斥量对象,线程就会进入等待状态,但是如果系统检查想要获得互斥量的线程的ID与互斥量对象内部记录的线程ID相等那么此时系统也会让线程保持可调度状态--即使该互斥量尚未触发.

■ 互斥量与其它内核对象不同的根本原因是"所有权"问题:也就是说,其它内核对象不会记住自己是哪个线程等待成功的.这就使得他即使在未触发状态下也能为线程所得.

这样就会出现一种情况是,如果占用互斥量的线程在释放互斥量之前终止,那么互斥量将出现"遗弃".

由于系统记录着所有的互斥量和线程内核对象,所以他有能力使得被遗弃的互斥量重新变为触发状态.这样其他的等待互斥量的线程就可以获取到互斥量的资源.但是这里有一个小小的不同:处于等待的函数不在返回通常的WAIT_OBJECT_0而是返回WAIT_ABANDONED.

■ 互斥量和关键段的区别:

特征

互斥量

关键段

是否能跨进程使用

声明

HANDLE hmtx

CRITICAL_SECTION cs

初始化

Hmtex = CreateMutex(NULL,FALSE,NULL)

InitializeCriticalSection(&cs)

清理

CloseHandle(hmtx)

DeleteCriticalSection(&cs)

无限等待

WaitForSignalObject(hmtx,INFINITE)

EnterCriticalSection(&cs)

0等待

WaitForSignalObject(hmtx,INFINITE)

TryEnterCriticalSection(&cs)

任意时间长度等待

WaitForSingleObject(hmtx,dwMilliseconds)

不支持

释放

ReleaseMutex(hmtx)

LeaveCriticalSection(&cs)

能否同时等待其他内核对象

是(使用WaitForMultipleObject或者类似的函数)

线程同步对象速查表:

对象

何时处于未触发状态

何时处于触发状态

成功等待的副作用

进程

进程仍在运行

进程终止时(ExitProcess或者TerminateProcess)

没有

线程

线程仍在运行时

线程终止的时候(Exit(Terminate)Thread

没有

作业

作业尚未超时时

作业超时时

没有

文件

有待处理的I/O请求的时

I/O请求完成时

没有

控制台输入

没有输入时

有输入时

没有

文件变更通知

文件没有变更通知时

文件系统检测到变更时

重置通知

自动重置事件

ResetEvent,PulseEvent或等待成功时

SetEvent/PulseEvent被调用时

重置事件

手动重置事件

ResetEvent,PulseEvent

SetEvent/PulseEvent被调用时

没有

自动重置可等待计时器

CancelWaitableTimer或者等待成功的时候

时间到时(SetWaitableTimer)

重置计数器

手动重置可等待计时器

CancelWaitableTimer

时间到时(SetWaitableTimer)

没有

信号量

等待成功时

计数大于0(ReleaseSemaphore)

计数器减一

互斥量

等待成功的时候

不为线程占用时(ReleaseMutex)

把所有权交给线程

关键段(用户模式)

等待成功时((Try)EnterCriticalSection)

不为线程占用时(LeaveCriticalSection)

把所有权交给线程

SRWLock(用户模式)

等待成功时(AcquireSRWLock(Exclusive))

不为线程占用时(ReleaseSRWLock(Exclusive))

把所有权交给线程

条件变量(用户模式)

等待成功时(SleepConditionVariable*)

被唤醒时(Wake(All)ConditionVariable)

没有

Interlocked系列函数(用户模式)从来不会使得线程变为不可调度状态,它们只是修改一个值,并立即返回.

8 异步设备I/O:允许线程开始读取操作或者写操作,而不需等待读取操作或者等待操作完成.设备对象时可同步的内核对象,即我们可以调用WaitForSignalObject,并传入文件句柄、套接字、通信端口等等。当系统执行异步的I/O的时候,设备处于为触发状态,一旦操作完成,系统将变成触发状态。这样线程就知道操作已经完成了,线程就可以继续执行。

函数:DWORD WaitForInputIdle(

HANDLE hProcess,

DWORD  dwMillseconds);

先挂起当前进程,然后等待hProcess标示的进程,直到创建应用程序第一个窗口的(也就是hProcess标示的进程)线程中没有待处理的输入为止.

线程也可以调用MsgWaitForMultipleObjects或MsgWaitForMultipleObjsEx,使得线程等待需要自己处理的消息.

DWORD MsgWaitForMultipleObjects(

DWORD dwCount,

PHANDLE phObjects,

BOOL bWaitAll,

DWORD dwMilliseconds,

DWORD dwWakeMask);

DWORD MsgWaitForMultipleObjectsEx(

DWORD dwCount,

PHANDLE phObjects,

DWORD dwMilliseconds,

DWORD dwWakeMask,//标示是哪类消息.

DWORD dwFlags);

这些函数与WaitForMultipleObjects类似,不同之处在于,不仅内核对象被触发的时候调用线程会变成可调度状态,而且当窗口消息需要被派送到一个由调用线程创建的窗口时,他们也会变成可调度状态.

创建窗口的线程和执行与用户界面相关的任务线程都不应该使用WaitForMultipleObjects,而应该使用MsgWaitForMultipleObjectsEx.因为前者会妨碍线程对用户在用户界面上的操作进行响应.

9. 当调试器开始执行的时候,会将自己附着到被调试的程序.然后调试器只是在一边闲着,等待操作系统通知它有与被调试程序相关的时间发生.调试器通过WaitForDebugEvent函数类等待这些时间:

BOOL WaitForDebugEvent(

PDEBUG_EVENT pde,

DWORD  dwMillsecond);

当调试器调用这个函数的时候,调试器的线程会挂起,系统通过让WaitForDebugEvent返回的方式,来通知调试器有调试事情发生.参数pde指向的结构包含了与刚才发生的调试事件有关的信息.

10. SignalObjectAndWait函数通过一个原子操作来触发内核对象并等待另一个内核对象:

DWORD SignalObjectAndWait(

HANDLE hObjectToSignal,//标示的必须是一个互斥量、信号量或者事件.

HANDLE hObjectToWaitOn,//标示互斥量、信号量、事件、计时器、进程、线程、 //作业、控制台输入以及变更通知.

DWORD dwMilliseconds,//最多应该花多长时间来等待对象触发

BOOL bAlertable); //当线程处于等待状态时,是否能够对添加到队列中的异步 //过程调用进行处理

hObjectToSignal:任何其它类型对象将导致函数返回WAIT_FAILED.这时使用GetLastError会返回ERROR_INVALID_HANDLE.该函数内部会检查对象的类型并分别执行与ReleaseMutex、ReleaseSemaphore(使用计数减一)或者SetEvent等价的操作.

返回值:WAIT_OBJECT_0,WAIT_OBJECT_TIMEOUT,WAIT_FAILED,WAIT_ABANDONED或者WAIT_IO_COMPLETION.

函数的优点:花费时间短.

10. Vista提供了一组新的等待链遍历(简称WCT)API,这些函数可以让我们列出所有锁,并检测进程内部,甚至是进程之间的死锁.

可能的锁

描述

关键段

Windows会记录哪个线程正在占用哪个关键段

互斥量

Windows户记录哪个线程正在占用哪个互斥量,即使被遗弃

进程和线程

Window会记录哪个线程正在等待进程终止或者线程终止

SendMessage调用

知道那个线程正在等待SendMessage调用返回时很重要的

COM初始化和调用

Windows会记录对CoCreateInstance的调用以及对COM对象的方法的调用

高级本地过程调用

在windows Vista中,作为新的未公开的内核进程间通信机制,ALPC已经取代了本地过程调用.

剩下一点,运行xp无法调试起来,很悲剧.明天再看