Drecik学习经验分享
转载请注明出处:http://blog.csdn.net/drecik__/article/details/8105302
互斥量内核对象用来确保一个线程独占对一个资源的访问,该对象包含一个使用计数、线程ID以及递归计数。
互斥量的行为和关键段完全相同,但是互斥量是内核对象,而关键段是用户模式下的,所以互斥量比内核对象慢的多。但不同进程中的线程可以访问同一个互斥量,还可以在等待对资源的访问权时可以指定一个最长等待时间。
线程ID是标识当前占用这个互斥量是哪个线程,递归计数标识这个线程占用该互斥量的次数。
互斥量有以下规则:
创建,打开和释放互斥量内核对象:
- 如果线程ID为0,那么该互斥量不为任何线程占用,他处于触发状态
- 如果线程ID为非0值,那么有一个线程已经占用了该互斥量,它处于未触发状态
- 操作系统允许线程在占有互斥量的时候未经释放就终止(在后面讨论)
HANDLE CreateMutexW( LPSECURITY_ATTRIBUTES lpMutexAttributes, // 安全属性; BOOL bInitialOwner, // 是否将互斥量的线程ID设为创建的该线程ID; LPCWSTR lpName // 资源名称,可为NULL; ); HANDLE OpenMutexW( DWORD dwDesiredAccess, // 打开权限; BOOL bInheritHandle, // 是否可以被继承; LPCWSTR lpTimerName // 创建时的名称,不可以为NULL; ); BOOL ReleaseMutex( HANDLE hMutex );为了获得对保护资源的访问权,线程要调用等待函数并传入互斥量句柄,在内部,等待函数会检查线程ID是否为0,如果为0则把线程ID设为调用的线程ID,把递归计数设为1,然后让该线程继续执行。如果检测到线程ID不为0,那么调用线程进入等待转台,当另一个线程将互斥量的线程ID设为0的时候,系统会唤醒其中一个正在等待的线程(所有线程都公平对待)。这些检测和修改都是以原子方式进行。
如果检测到线程ID即为该线程,即一个线程多次进入该资源,则会递增互斥量的递归计数,来表示访问的次数。
在访问资源结束后,调用ReleaseMutex释放互斥量,函数会将递归计数减1,如果等待互斥量不止一次,即递归计数大于1,则需要调用相同的ReleaseMutex才会释放互斥量。
接下来讨论系统对互斥量的特殊处理,因为互斥量有线程所有权概念,所以互斥量会知道哪个线程在访问它。所以它能够让互斥量在未触发的时候可以让同一个线程获得访问权。这个也适用于释放互斥量的时候,释放时会检测互斥量的ID是否与调用线程一致,如果一致则正常调用,否则返回FALSE。
如果占用互斥量的线程在释放互斥量之前终止,那么系统认为该互斥量被遗弃,系统会在其他等待该互斥量中选取一个线程变为可调度状态(所有线程公平对待),不同的是等待函数将返回WAIT_ABANDONED。
下表总结了各种线程同步的行为:
Interlocked系列函数(用户模式)从来不会使线程编程不可调度状态,它们只是修改一个值并立即返回。
异步设备I/O允许线程开始读取操作或写入操作,但不必等待读取操作或写入操作完成。
设备对象是可同步的内核对象,意味着我们可以调用WaitForSingleObject,当系统执行异步I/O的时候,设备对象处于未触发状态,一旦操作完成,系统会将对象变成触发状态,这样线程就知道操作完成了。
线程可以调用WaitForInputIdle函数将自己挂起:
DWORD WaitForInputIdle( HANDLE hProcess, // 进程句柄; DWORD dwMilliseconds); // 等待时间;这个函数会等待有hProcess标识的进程,直到创建应用程序第一个窗口的线程中没有待处理的输入位置。
该函数对父进程来说比较有用。父进程可以创建一个子进程来完成一些工作,当父进程调用CreateProcess的时候,父进程可以一边继续执行,一边让子进程初始化。父进程能够知道子进程初始化完毕才唯一方法就是等待自己称,直到它不再处理任何输入位置,因此父进程可以在调用CreateProcess之后调用WaitForInputIdle进行等待。
当我们需要强制在应用程序中发送消息的时候,也可以使用WaitForInputIdle。
例如可以发送一些耗时的消息,打不知道什么时候完毕并准备就绪,就可以使用WaitForInputIdle进行等待。
线程也可以调用MsgWaitForMultipleObjects或MsgWaitForMultipleObjectsEx,可以使线程等待需要自己处理的消息:
// 返回值; // WAIT_TIMEOUT :因时间终了而返回; // WAIT_OBJECT_0 :当bWaitAll是TRUE; // WAIT_OBJECT_0 to (WAIT_OBJECT_0 + nCount – 1) :bWaitAll是FALSE,将返回值减去WAIT_OBJECT_0,就表示数组中哪一个handle被激发了; // WAIT_ABANDONED_0 to (WAIT_ABANDONED_0 + nCount – 1) :等待的对象中有任何mutexes; // WAIT_FAILED :函数失败时返回该值; // WAIT_OBJECT_0 + nCount :消息到达队列; DWORD MsgWaitForMultipleObjects( DWORD nCount, // 表示pHandles数组的元素个数,最大容量是MAXIMUM_WAIT_OBJECTS; LPHANDLE pHandles, // 指向一个由对象handles组成的数组,这些handles的类型不需要相同; BOOL fWaitAll, // 是否等待所有的handles被激发才返回; DWORD dwMilliseconds, // 超时时间; DWORD dwWakeMask // 欲观察的用户输入消息类型,可以查看MSDN; ); DWORD MsgWaitForMultipleObjectsEx( DWORD nCount, // 句柄数组中句柄数目; LPHANDLE pHandles, // 指向句柄数组的指针; DWORD dwMilliseconds, // 以毫秒计的超时值; DWORD dwWakeMask, // 要等待的输入事件类型; DWORD dwFlags // 等待标志,具体查看MSDN; );它们与WaitForMultipleObjects函数类似,不同之处在于,该函数当有指定的窗口消息被派送到调度该函数的线程所创建的窗口时,可变为可调度状态。
所以创建窗口的线程执行与用户见面相关任务的线程应该使用这些函数,可以不会影响用户在界面上的操作。
Windows操作系统内建了绝佳的调试支持,当调试器开始执行的时候,会将自己附着到被调试程序,然后调试器只是在一边闲着,等待操作系统通知它有与被调试程序相关的事情发生
BOOL WaitForDebugEvent( LPDEBUG_EVENT lpDebugEvent, // 指向的结构包含了与刚才发生的调试事件有关的信息; DWORD dwMilliseconds // 等待时间; );
该函数会通过一个原子操作来触发一个内核对象并等待另一个内核对象:
// 返回值为WAIT_OBJECT_0,WAIT_TIME_OUT,WAIT_FAILED, WAIT_ABANDONCED; DWORD SignalObjectAndWait( HANDLE hObjectToSignal, // 触发的内核对象,只可以是互斥量,信号量或事件; HANDLE hObjectToWaitOn, // 等待的内核对象; DWORD dwMilliseconds, // 等待时间; BOOL bAlertable // 标识当线程处于等待的时候是否应该能够对添加到队列中的异步过程调用进行处理; );
该函数对于调用两个内核函数来说节省了大量的处理时间。