线程与内核对象的同步
1为什么使用内核对象实现线程同步
1.1用户模式下实现线程同步特点
(1)同步速度快
(2)互锁函数族只能对单值进行操作,无法是线程进入等待状态。
(3)临界区可以使线程进入等待状态,但是只能用于同一个进程的多个线程。由于无法设置超时操作,因此容易进入死锁状态。
1.2内核模式下实现线程同步特点
(1)内核对象机制的唯一缺点:速度慢。
(2)优点:适用性强
2等待函数
2.1 WaitForSingleObject
等待函数使线程进入等待状态,等待一个特定的内核对象变成已通知状态。当线程等待对象的状态为未通知状态时,不能调度这个,线程,一旦内核对象变成已通知状态,线程标志变成可调度。
DWORD WaitForSingleObject(
HANDLE hObject,
DWORD dwMilliseconds);
第一个参数hObject标识一个支持通知/未通知状态的内核对象;第二个参数dwMillseconds指明等待对象变成已通知状态所需的等待时间。当第二个参数为INFINITE(-1)时,说明线程可以无限等待,直到进程终止运行。通常情况下,会使用INFINITE,如果对象一直未通知,线程将一直处于死锁状态,但是不会浪费处理器时间。
函数返回值:
WAIT_OBJECT_0 //等待对象变成已通知状态
WAIT_TIMEOUT //等待超时
WAIT_FAILED //传递错误参数,通过GetLastError()了解详情
2.2 WaitForMultipleObject
DWORD WaitForMultipleObject(
DWORD dwCount,
CONSTHANDLE* phObjects,
BOOL fWaitAll,
DWORD dwMilliseconds);
第一个参数指明调用线程所查看的内核对象的数量,在0-MAXIMUM_WAIT_OBJECTS(windows定义为64)之间;第二个参数是指向内核对象句柄数组的指针;第三个参数fWaitAll指示两种成功等待方式,一种需要所有内核对象变成已通知,另一种需要任意一个内核对象变成已通知;第四个参数同上。
返回值:
WAIT_TIMEOUT;
WAIT_FAILED;
WAIT_OBJECT_0;(fWaitall为ture)
WAIT_OBJECT_0到WAIT_OBJECT_0+dwCount-1之间的值(fWaitall为false时,指示哪个对象变成已通知状态)
3事件内核对象
事件内核对象是所有内核对象中最基本的。事件内核对象包含一个使用计数(所有内核对象都有),一个布尔值,指示这个事件是自动重置事件还是人工重置事件。以及另外一个指示事件处于已通知状态还是未通知状态的布尔值。
两种事件对象:
人工重置事件:得到通知时,等待这个事件的所有线程变成可调度线程。
自动重置事件:得到通知时,等待这个事件的所有线程只有一个变成可调度线程。
3.1 创建事件内核对象
下面是用于创建事件内核对象的CreateEvent函数:
HANDLE CreateEvent(
PSECURITY_ATTRIBUTES psa,
BOOL fManualReset,
BOOL fInitialState,
PSTSTR pszName)
第一个参数(PSECURITY_ATTRIBUTES)安全性描述符:描述创建对象的进程,以及哪些进程可以访问对象,哪些不可以访问;第二个参数fManualReset参数是一个布尔值,用于指明创建人工重置事件还是自动重置事件;第三个参数fInitialState指明这个事件初始化为已通知状态还是未通知状态。第四个参数为事件命名;
3.2 内核对象的使用
其他进程中的线程可以通过pszName参数中传递的相同值,使用继承性,使用DuplicateHandle等函数来获得对这个对象的访问权。或者通过调用OpenEvent,传递的pazName一致即可。
HANDLE OpenEvent(
DWORD fdwAccess,
BOOL bInherit,
PCTSTRpszName);
创建事件后,就可以直接控制这个事件的状态。可以调用SetEvent将事件的状态改为已通知,调用ResetEvent改为未通知状态:
BOOL SetEvent(HANDLE hEvent);
BOOL ResetEvent(HANDLE hEvent);
不在需要使用该事件时,调用CloseHandle函数。
4 等待定时器内核对象
等待定时器是在某个时间或按照规定时间间隔,发出自己的信号通知的内核对象。通常使用这些内核对象,在某个时间执行某个操作。
4.1创建等待定时器
HANDLE CreateWaitableTimer(
PSECURITY_ATTRIBUTES psa,
BOOL fManualReset,
PSTSTR pszName);
进程也可以通过OpenWaitableTimer函数,来获得与进场相关的等待定时器句柄。
HANDLE OpenWaitableTimer(
DWORD dwDesiedAccess,
BOOL bInheritHandle,
PSTSTR pszName)
4.2使用定时器
等待定时器总是设置为未通知状态。可以调用SetWaitableTimer函数,来确定定时器何时变成已通知状态:
BOOLSetWaitalbeTimer(
HANDLE hTime,
const LARGE_INTERGER*pDueTime
LONG lPeriod,
PTIMERAPCOUTINE pfnCompletionRoutine,
PVOID pvArgToCompletionRoutine,
BOOL fResume);
hTime:指明要设置的定时器对象
pDueTime:指明定时器第一次报时时间;
pPeriod:指明定时器第一次报时之后,定时器再次报时的时间间隔
5.信号量内核对象
信号量内核对象用来对资源进行计数,它与其他内核对象一样,包含一个使用计数。同时包含两个带符号的32位数:一个最大资源数量,一个当前资源数量。
信号量的使用规则:
l 当前资源的数量大于0时,发出信号量信号;
l 当前资源数量等于0时,不发出信号量信号;
l 当前资源数量不能为负值;
l 当前资源数量不能大于最大资源数量
使用信号量时,不能将信号量的对象的使用数量和当前资源数量混淆。
5.1信号量创建
下面是创建信号量内核对象的函数:
HANDLECreateSemaphore(
PSECURITY_ATTRIBUTE psa,
LONG lInitialCount,
LONG lMaximumCount,
PCTSTR pszName);
llMaximumCount表示应用程序处理的最大资源数量,这个参数是带符号的32位值,因此最多可以拥有2147483647个资源;
lInitialCount参数指明开始时(当前)资源中有多少资源可供使用。
调用OpenSemaphore函数,另外的进程也能获取自己与现有信号量相关的句柄:
HANDLEOpenSemaphore(
DWORD fdwAccess,
BOOL bInitialCount,
PCTSTR pszName);
可以调用ReleaseSemphore函数,对信号量的当前资源数量进行递增:
BOOLReleaseSemaphore(
HANDLE hsem,
LONG lReleaseCount,
PLONG plPreviousCount);
这个函数的功能是将参数lReleaseCount的值添加给信号量当前的资源数量,通常为1.没有函数可以查询信号量当前的资源数量。
具体题目: 在操作系统的进程管理中,若系统中有10个进程使用互斥资源R,每次只允许3个进程进入互斥段(临界区),则信号量S的变化范围是______(1):若信号量S的当前值为-2,则表示系统中有______(2)个正在等待该资源的进程。 (1)A.-7~1 B.-7~3 C.-3~0 D.-3~10 (2)A.0 B.1 C.2 D.3
B:S<0后请求R的进程将被阻塞,此时应该有3个进程获得资源。 C:第一个分配后,S=2;第三个分配后,S=0;第四个进程请求时S=-1,等待资源;S=-2时既有两个进程在等待。 关键是要分清:先S减一,还是先分配资源
6.互斥对象内核对象
6.1互斥对象
互斥对象确保线程能够互斥的访问一个资源,这也是这个对象如此命名的原因。互斥对象包含一个使用计数,一个线程ID和一个递归计数器。
互斥对象的使用规则如下:
l 若线程ID是0,即无效ID,互斥对象不能被任何线程拥有。并且发出这个对象的通知信号。
l 若线程ID是个非0值,那么互斥对象被某一个线程拥有,并且不发出这个对象的通知信号。
l 互斥对象在操作系统中拥有特殊的代码,允许互斥对象违反正常的规则,这与其他内核对象不同。
使用互斥对象之前,调用CreateMutex函数创建互斥对象。
HANDLECreateMutex(
PSECURITY_ATTRIBUTES pas;
BOOL fInitialOwner,
PCTSTR pszName);
另外的进程通过Ope nMutex获得自身与现有互斥对象关联的句柄
HANDLEOpenMutex(
DWORD fdwAccess,
BOOL bIniheritHandle,
PCTSTR pszName);
互斥对象所进行的所有检查和修改操作都是以原子方式进行的。一旦线程等待到了一个互斥对象,线程就拥有对所保护资源的独占访问权。线程调用ReleaseMutex函数释放互斥对象,将互斥对象的引用计数-1。
BOOL ReleaseMutex(HANDLE hMutex);
“异常”:对于互斥对象来说,存在内核对象已通知或未通知的异常情况。假如一个线程正在等待一个未通知的互斥对象,那么通常将这个线程置于等待状态。然而系统会查看试图获得互斥对象的线程ID,并与互斥对象记录的线程ID进行比较.如果两者相同,那么即使互斥对象处于未通知状态,系统也允许这个线程保持可调度状态。
这个“异常”情况不适用于其他内核对象。因此要使递归计数器的值大于1,唯一的方法是使线程多次等待相同的互斥对象。释放时需要多次调用ReleaseMutex,直到计数器的值为0。
6.2 释放问题
互斥对象和其他内核对象不同,有一个“线程所有权”的问题,其他内核对象无法记住哪个线程正在拥有这个对象,而互斥对象能够对此保持跟踪。这也是互斥对象拥有异常规则的原因。异常规则是的线程在对象没有发出通知时,获取这个对象。
当拥有互斥对象的线程在释放这个对象前终止运行,系统则把这个互斥对象视为已经放弃。然后系统检查是否有等待这个互斥对象的线程,并选择其中一个。将互斥对象的线程ID设置为选定线程的ID,计数为1.
在这个过程中,等待函数返回的不是WAIT_OBJTCT_0,而是WAIT_ABANDONED,表示这个互斥对象被另一个线程占有,而另一个进程在释放对象前终止。