Windows核心编程笔记(7)----内核模式下的线程同步

1、内核对象同步与用户模式下同步对比

使用内核对象的唯一缺点就是性能,调用内核对象函数时,调用线程必须从用户模式切换到内核模式,这种切换是相当
耗时的。
内核对象(进程、线程、作业)要么处于触发态,要么处于未触发状态。进程内核对象在创建时总是处于未触发状态,
当进程终止时,操作系统会自动使进程内核对象变成触发状态。当进程内核对象处于触发状态后,将永远保持这种状态,
再也不能变回未触发状态。

2、等待内核对象

WaitForSingleObject等待单个内核对象,WaitForMultipleObject等待多个内核对象,最多支持同时等待MAXIMUM_WAIT_OBJECTS
(64)个内核对象。
WaitForMultipleObject等待多个内核对象其中一个触发时,返回值如果既不是WAIT_TIMEOUT也不是WAIT_FAILED,那么用
返回值减去WAIT_OBJECT_0就是已经触发的那个内核对象的索引值了。

3、创建事件

查看MSDN
HANDLE WINAPI CreateEvent(
__in          LPSECURITY_ATTRIBUTES lpEventAttributes,
__in          BOOL bManualReset,
__in          BOOL bInitialState,
__in          LPCTSTR lpName
);
bManualReset: 是否手动设置事件触发状态,FALSE则由系统来自动设置。
bInitialState: 是否初始化状态,TRUE则事件为触发状态,FALSE则为未触发状态
lpName: 事件名称,可为NULL;如果该事件名已经被创建,则返回具有EVENT_ALL_ACCESS权限的事件句柄。
其他进程中的线程访问该事件对象的方法:使用继承、使用DuplicateHandle、调用OpenEvent打开指定名称的事件
更改事件状态:
SetEvent 设置事件为触发状态
ResetEvent 设置事件为未触发状态

4、可等待计时器内核对象

CreateWaitableTimer创建完成后内核对象处于未触发状态,需要使用API
BOOL WINAPI SetWaitableTimer(
__in          HANDLE hTimer,
__in          const LARGE_INTEGER* pDueTime,
__in          LONG lPeriod,
__in          PTIMERAPCROUTINE pfnCompletionRoutine,
__in          LPVOID lpArgToCompletionRoutine,
__in          BOOL fResume
);
来设置计时器对象的一些属性,pDueTime是第一次触发时间(UTC时间),lPeriod表示在第一次触发后计时器应该
以什么样的频率触发。

5、信号量

创建信号量内核对象
HANDLE WINAPI CreateSemaphore(
__in          LPSECURITY_ATTRIBUTES lpSemaphoreAttributes,
__in          LONG lInitialCount,
__in          LONG lMaximumCount,
__in          LPCTSTR lpName
);
lInitialCount指定信号量的可用资源数,lMaximumCount表示信号量的最大资源计数
信号量的规则如下:
1、如果当前的资源计数大于0,那么信号量处于触发状态;
2、如果当前资源计数等于0,那么信号量处于未触发状态;
3、系统不会让当前资源计数小于0;
4、当前资源计数绝不会大于最大资源数。

6、互斥量对象

互斥量对象包含一个引用计数、线程ID和一个递归计数。线程ID用来记录当前占用这个互斥量的线程的ID,递归计数用来表示这个
线程占用该互斥量的次数。
互斥量对象与临界区区别:
互斥量是内核对象,临界区在用户模式下的线程同步,意味着互斥量比临界区慢,不同进程中的线程可以访问同个互斥量,
线程可以在等待资源的访问权时指定一个等待时间。
顺便查了下用户模式与内核模式的区别:
1、应用程序在用户模式下运行,操作系统核心组件运行在内核模式下。多个驱动程序在内核模式下运行,但某些驱动程序
在用户模式下运行;
2、当启动用户模式的应用程序时,Windows为该程序创建进程,进程为应用程序提供专属的”虚拟地址空间“和专用的”句柄表“
由于应用程序的虚拟地址空间为专用空间,一个应用程序无法更改属于其他应用程序的数据。
3、在内核模式下运行的所有代码都共享单个虚拟地址空间,如果内核模式驱动程序损坏,则整个操作系统会损坏。
创建
HANDLE WINAPI CreateMutex(
__in          LPSECURITY_ATTRIBUTES lpMutexAttributes,
__in          BOOL bInitialOwner,
__in          LPCTSTR lpName
);
bInitialOwner控制互斥量的初始状态,如果传入FALSE,那么互斥量对象的线程ID和递归计数都将被设定为0。
意味着互斥量不为任何线程占用,因此处于触发状态;传入TRUE,那么对象的线程ID将被设置为调用线程的ID,
递归技术将被设置为1.由于线程ID不为0,因此互斥量处于未触发状态。


互斥量与其他内核对象不同,它具有”线程所有权“。线程调用ReleaseMutex时候,函数会检查调用线程的ID与互斥量内部保存的ID
是否一致,如果不一致,函数不执行任何操作返回FASLE(试图释放的互斥量不属于调用者)。
如果占用互斥量的线程在释放互斥量之前终止,系统会认为互斥量被遗弃。互斥量被遗弃后,系统会设置互斥量对象的ID为0,将它的
递归计数设置为0。然后检测是否有其他线程正在等待该互斥量,如果有,调度该线程。

7、其他线程同步函数

1、异步设备I\O,设备对象是可同步的内核对象,可以使用WaitForSingleObject并传入文件句柄、套接字、通信端口等等。系统执行
异步I\O时,设备对象处于未触发状态,一旦I\O操作完成,设备对象就被触发。
2、WaitForInputIdle,等待指定进程,知道创建应用程序第一个窗口的线程中没有待处理的输入为止。主要用在父进程创建子进程时。
3、WaitForDebugEvent
4、SingleObjectAndWait 函数会触发一个内核对象并等待另一个内核对象。
DWORD WINAPI SignalObjectAndWait(
_In_  HANDLE hObjectToSignal,
_In_  HANDLE hObjectToWaitOn,
_In_  DWORD dwMilliseconds,
_In_  BOOL bAlertable
);
hObjectToSignal:需要触发的内核对象句柄,只能是互斥量、信号量或者事件,其他任何对象将导致函数返回错误;
hObjectToWaitOn:需要等待的内核对象句柄,可以是互斥量、信号量、事件、计时器、进程、线程、作业控制台输入以及变更通知;
bAlertable:当线程处于等待状态时,是否能够对添加到队列中的异步过程调用进行处理。(是否可以执行APC回调)
好处在于执行一个函数比两个函数效率更高,而且从用户模式切换到内核模式将耗费很多CPU事件,还有线程的调度也是很耗时的
//一下代码耗费大量CPU时间
ReleaseMutex(hMutex);
WaitForSingleObject(hEvent, INFINITE);

使用SignalObjectAndWait即可代替这两句代码。在高性能服务器应用程序中,SignalObjectAndWait可以节省大量的处理时间。

测试代码:

HANDLE g_hSemaphore;
volatile LONG g_nThreadIndex = 0;

UINT WINAPI HelpThread(LPVOID lpParam)
{
	//这里给每个线程来一个唯一的ID标识
	InterlockedExchangeAdd(&g_nThreadIndex, 1);
	int nCurIndex = g_nThreadIndex;
	cout<<"进入线程,线程ID="<
	//测试多个线程抢占信号量资源
	for (; i<6; ++i)
	{
		hThreads[i] = (HANDLE)_beginthreadex(NULL, 0, HelpThread, NULL, 0, NULL);
	}
	WaitForMultipleObjects(6, hThreads, TRUE, INFINITE);
	//关闭线程句柄
	for ( i=0; i<6; ++i )
		CloseHandle(hThreads[i]);
	cout<<"辅助线程执行完毕…………"<


你可能感兴趣的:(Windows核心编程)