(6)内核模式下的线程同步
用户模式主要优点为速度快,内核模式相对于用户模式的区别是可以对多个值进行操作,也可以对不同进程下的线程进行同步操作,但是速度会比较慢(模式的切换,高速缓存命中率下降)。
几乎所有的内核对象都可以进行线程同步。内核对象内部有两种状态,一种是未触发状态(比如,进程内核对象刚创建的时候)和触发状态(比如,对应进程终止的时候),每个内核对象内部有一个bool值来监控这两种状态。
//该函数创建一个线程等待一个内核对象被触发
//hObject表示要等待的内核对象
//dwMilliseconds表示等待的最长时间
DWORD WatiForSingleObject(HANDLE hObject,DWORD dwMilliseconds);
DWORD dw=WaitForSingleObject(hProcess,5000);
switch(dw)
{
cse WAIT_OBJECT_0://规定的时间内等待成功。
break;
case WAIT_TIMEOUT://超时。
break;
case WAIT_FAILED://指定的句柄无效。
break;
Default:
break;
}
//和上述函数类似,不过允许一个线程同时等待多个内核对象
DWORD WaitForMultipleObjects(DWORD dwCount,CONST HANDLE*pbObject,
BOOL bWaitAll,DWORD dwMilliseconds);
HANDLE h[3];
H[0]=hProcess1;
H[1]=hProcess2;
H[2]=hProcess3;
DWORD dw=WaitForMultipleOBjecs(3,H,false,5000);
switch(dw)
{
case WAIT_OBJEC_0://第一个对象被触发。
break;
case WAIT_OBJEC_0+1://第二个对象被触发。
break;
case WAIT_OBJEC_0+2://第三个对象被触发。
break;
case WAIT_TIMEOUT://超时
break;
case WAIT_FAILED://句柄无效。
break;
}
(1)通过事件内核对象实现同步
事件内核对象:通过内核对象的触发来表示一个操作完成。
分为手动重置事件对象(在变为触发状态后需要人为变回非触发状态)和自动重置事件对象(在变为触发状态后会自动变为非触发状态)
一个手动重置事件对象触发时,所有等待此对象的线程都变成了可调度状态。而一个自动重置事件变成触发时在所有等待该内核对象触发的线程中只有一个可以变成可调度状态。
要实现跨进程的内核对象的同步首先必须要获得其他进程的内核对象,有以下三种方式
1:调用CreateEvent和OpenEvent时,指定pszName与已存在的事件内核对象同名。此时与OpenEvent功能相同。
2:使用继承。
3:使用DuplicateHandle函数。
// CreateEvent成功后将返回一个事件内核对象的句柄。
HANDLE CreateEvent(
//内核对象安全结构
PSECURITY_ATTRIBUTES psa,
// 指定要创建的内核对象是手动重置还是自动重置内核对象
BOOL bManualReset,
// 指定事件对象的初始状态。触发或未触发
BOOL bInitialState,
//指定该内核对象的名称。可用于跨进称共享内核对象
PCTSTR pszName);
//打开pszName指定的事件内核对象
HANDLE OpenEvent(
//指定对事件对象的请求访问权限,
//如果安全描述符指定的对象不允许要求通过对调用该函数的过程,函数将返回失败。
DWORD dwDesiredAccess,
//指定是否返回的句柄是否继承 。该参数必须设置为false
BOOL bInherit,
//要打开内核对象的名称
PCTSTR pszName);
//使内核对象处于触发状态
BOOL SetEvent(HANDLE hEvent)
//使内核对象处于非触发状态
BOOL ResetEvent(HANDLE hEvent)
使用上述函数的组合则可以实现常见的同步操作比如
//定义的事件句柄的变量
HANDLE g_hEvent;
int main()
{
//创建一个事件句柄,一开始是未触发状态
g_hEvent=CreateEvent(NULL,true,false,NULL);
//创建一个新线程
HANDLE hThread=CreateThread(NULL,0,Thread1,NULL,0,NULL);
//打开文件并读取内存。
//通知Thread1开始运行。
SetEvent(g_hEvent);
//其他操纵。
}
DWORD WINAPI Thread1(PVOID param)
{
//监听g_hEvent事件内核对象
WatiForSingleObject(g_hEvent,INFINITE);
//访问内存。
ResetEvent(g_hEvent);
}
(2)通过可等待的计时器内核对象实现同步
相关函数如下
//创建计时器内核对象,刚创建的计时器内核对象总是处于未触发状态
HANDLE CreateWaitableTimer(
//安全结构
PSECURITY_ATTRIBUTES psa,
//指定是手动重置计时器还是自动重置计时器
//当手动重置计时器被触发,正在等待该计时器的所有线程都变成可调度状态。
//当自动重置计时器被触发时,只有一个等待该计时器的线程变成可调度状态。
BOOL bManualReset,
//文件设备名
PCTSTR pszName);
//打开一个已存在的计时器内核对象
HANDLE OPenWaitableTimer(
//指定访问权限
DWORD dwDesiredAceess,
//指定是否可继承
BOOL bInheritHandle,
//文件设备名
PCTSTR pszName);
//由于刚创建的计时器是未触发的,此函数触发指定计时器
BOOL SetWaitableTimer(
//指定要触发的计时器
HANDLE hTimer,
// 计时器第一次触发的时间毫秒为单位
Const LARGE_INTEGEr*pDueTime,
// lPeriod表示在第一次触发之后,计时器的触发频度,毫秒为单位
LONG lPeriod,
PTIMERRAPCROUTING pfnCompletionRoutine,
PVOID pvArgToCompletionRoutine,
// bResume用以支持挂起。一般都传入false。当传入true时,
// 当计时器被触发时,系统就会使机器结束挂起模式,
// 并唤醒等待该计时器的线程。当为false时,计时器会被触发
// ,但是如果此时机器处于挂起态时,在机器继续执行之前,被唤醒的任何线程都得不到cpu。
BOOL bResume);
// 句柄所标识的计时器取消。此后计时器就不会再触发。
// 要想让计时器继续触发,可以再次调用SetWaitableTimer。
BOOL CancelWaitableTimer(HANDLE hTimer);
(3)用户计时器
通过SetTimer来设置。
用户计时器需要在用户程序中使用大量的用户界面基础设施,从而消耗更多的资源。
而可等待计时器是内核对象,不仅可以在多线程间共享而且具备安全性 。
用户计时器会产生WM_TIMER消息,这个消息被送到SetTimer设置的回调函数。
此时只有一个线程得到通知。而可等待计时器对象可以被多个线程等待。
如果打算在计时器被触发时执行与用户界面相关的操作。使用用户计时器可使代码更容易编写。
(4)信号量内核对象
信号量内核对象是用来对资源进行统计的。它包含使用计数,最大资源(可控制的最大资源)和当前资源计数(当前可用的资源计数)
//创建一个信号量内核对象
HANDLE CreateSomaphore(
//安全结构
PSECURITY_ATTRIBUTE psa,
//初始资源
LONG lInitialCount,
//最大资源
LONG lMaximuCount,
//文件设备名
PCTSTR pszName);
//打开一个信号量内核对象,前两个参数与前面一样
HANDLE OpenSemaphore(
DWORD dwDesiredAccess,
BOOL hInheritHandle,
//文件设备名
PCTSTR pszName);
当一个线程需要保护资源时内部的等待函数会通过传入的信号量内核对象自动检查当前的资源计数,如果大于0则分配资源,如果为0则线程等待。
当有线程调用下面函数让计数增加时,等待的线程执行。
BOOL ReleaseSemaphore(
HANDLE hSemaphore,
// 被加到当前资源计数的值一般为1
LONG lReleaseCount,
// 返回加之前的值
PLONG plPreviousCount);
(5)互斥量内核对象
确保一个资源只被一个线程独占。
互斥量包含一个使用计数,线程ID,和一个递归计数。
互斥量比关键段慢,但是互斥量可以同时被多个进程使用。可用于跨进程的线程同步。
线程ID表示当前占有该互斥量的是哪个线程,递归计数表示同一个线程递归引用此互斥变量的次数。使用计数表示该互斥量的所有使用次数。
互斥量内核对象中的线程ID如果为0则此互斥量没有被任何线程引用。不为0则被当前线程ID指定的线程引用。
互斥量内核对象常用函数
//创建互斥量内核对象
HANDLE CreateMutex(
PSECURITY_ATTRIBUTES psa,
// 控制互斥量的初始状态,如果为false则不为任何线程拥有,为true则为调用线程拥有
BOOL bInitialOwner,
// 文件设备名
PCTSTR pszName);
//打开已存在的互斥量内核对象
HANDLE OpenMutex(
DWORD dwDesiredAccess,
BOOL bInheritHandle,
PCTSTR pszName);
//释放一个互斥量对象ReleaseMutex时该函数会检查调用线程ID是否与互斥量内部保存的线程ID相同。
// 如果相同,那么递归计数会递减
// 如果线程成功等待了互斥量对象不止一次,
// 那么线程必须调用相同次数的ReleaseMutex才能使对象的递归计数变成0。
BOOL ReleaseMutex(HANDLE hMutex);
如果一个线程在没有释放互斥量之前就结束了运行,系统会检查出该遗弃互斥量,并将该互斥量的线程ID设置为0,表示没有被线程引用。如果有其他线程在等待此互斥量,这将互斥量分配给它。唯一的区别是等待函数不再返回WAIT_OBJEC_0,而是返回WAIT_ABANDONED