可等待的计时器内核对象
概述
- 可指定的计时器内核对象会在某个指定的时间触发,或者每隔一段时间触发一次,通常被用来在某个时间执行一些操作。
函数
1 2 3 |
HANDLE CreateWaitableTimer(PSECURITY_ATTRIBUTES, psa, BOOL bManualRest, PCTSTR pszName); |
- 第二个参数表示将要创建的是一个手动重置计时器还是一个自动重置计时器。
- 当手动重置计时器被触发的时候,正在等待该计时器的所有线程都会变成可调度状态。
- 当自动重置计时器被触发的时候,只有一个正在等待该计时器的线程会变成可调度状态。
- 在创建的时候,可等待计时器总是处于未触发状态。
1 2 3 |
HANDLE OpenWaitableTimer(DWORD dwDesiredAccess, BOOL bInheritHandle, PCTSTR pszName); |
- 用来得到一个已经存在的可等待计时器的句柄,该句柄与当前进程相关联。
1 2 3 4 5 6 |
BOOL SetWaitableTimer(HANDLE hTimer, const LARGE_INTEGER* pDueTime, LONG lPeriod, PTIMERAPCROUTINE pfnCompletionRoutine, PVOID pvArgToCompletionRoutine, BOOL bResume); |
- 可以调用这个函数触发计时器。
- 第一个参数表示要触发的计时器。
- pDueTime表示计时器第一次触发的时间应该在什么时候。
- lPeriod表示在第一次触发后,计时器应该以怎样的频度触发。
- lPeriod传0,则该计时器变成一个一次性计时器。只触发一次,之后再也不触发,除非再次调用这个函数设置。
- 最后一个参数
- 传TRUE,如果机器正处于挂起模式,当计时器触发的时候,系统会使机器结束挂起模式。
- 传FALSE,计时器会被触发,但在机器继续执行之前,被唤醒的任何线程都得不到CPU时间。
1 |
BOOL CancelWaitableTimer(HANDLE hTimer); |
- 取消句柄所标识的计时器。
给可等待的计时器添加APC调用
- 通常,我们调用SetWaitableTimer的时候,会给pfnCompletionRoutine和pvArgToCompletionRoutine这两个参数传NULL。
- 传NULL的话,该函数知道当时间到了,应该触发计时器对象。
- 如果希望时间一到,就让计时器把一个APC(异步过程调用)添加到队列中去,那就应该传一个计时器APC函数的地址过去。
1 2 3 4 5 6 7 |
// 计时器APC函数 VOID APIENTRY TimerAPCRoutine(PVOID pvArgToCompletionRoutine, DWORD dwTimerLowValue, DWORD dwTimerHighValue) { // do something } |
- 计时器被触发的时候,当且仅当SetWaitableTimer的调用线程正处于可提醒状态时,这个上面函数会被同一个线程调用。
也就是说,线程必须是由于调用SleepEx、WaitForSingleObjectEx、WaitForMutipleObjectEx、MsgWaitForMultipleObjectsEx或SignalObjectAndWait而进入的等待状态。如果线程并不是在这些函数中的一个函数内等待,那么系统是不会把计时器的APC函数添加到队列中的。 - 当计时器被触发的时候,如果线程处于可提醒状态,系统会让线程调用回调函数
- 回调函数的第一个参数就是传给SetWaitableTimer的第四个参数的值
- 回调函数的其余两个参数表示计时器被触发的时间
注意
- 只有当所有的APC函数都处理完毕后,才会返回可警告函数。因此,我们必须确保自己的TimerAPCRoutine函数会在计数器再次被触发之前结束。
实例
1 2 3 4 5 6 7 8 9 |
void SomeFunc() { HANDLE hTimer = CreateWaitableTimer(NULL, TRUE, NULL); LANG_INTEGER li = {0}; SetWaitableTimer(hTimer, &li, 5000, TimerAPCRoutine, NULL, FALSE); SleepEx(INFINITE, TRUE); CloseHandle(hTimer); } |
区别于用户计时器
- 用户计时器需要在应用程序中使用大量的用户界面基础设施,从而消耗更多的资源。
- 可等待计时器是内核对象,不仅可以在多个线程间共享,而且可以具备安全性。
- 用户计时器会产生WM_TIMER消息,这个消息会被送回调用SetTimer的线程或者送回创建窗口的线程。因此,当一个用户计时器被触发时,只有一个线程会得到通知;
而可等待计时器,多个线程可以一起等待,并且当可等待计时器是手动重置计时器时,有多个线程可以变成可调度状态。
信号量内核对象
概述
- 信号量内核对象用来对资源进行计数。
- 是原子方式的。
内容
- 一个使用计数。
- 一个最大资源计数(32位的值)。
- 表示信号量可以控制的最大资源数量
- 一个当前资源计数(32位的值)。
- 表示信号量当前可用资源的数量
规则
- 如果当前资源计数大于0,信号量处于触发状态。
- 如果当前资源计数等于0,信号量处于未触发状态。
- 系统绝对不会让当前资源计数变为负数。
- 当前资源计数绝不会大于最大资源计数。
函数
1 2 3 4 |
HANDLE CreateSemaphore(PSECURITY_ATTRIBUTES psa, LONG lInitialCount, LONG lMaximumCount, PCTSTR pszName); |
1 2 3 4 5 6 |
HANDLE CreateSemaphoreEx(PSECURITY_ATTRIBUTES psa, LONG lInitialCount, LONG lMaximumCount, PCTSTR pszName, DWORD dwFlags, DWORD dwDesiredAccess); |
- dwFlags是系统保留参数,应设为0
- dwDesiredAccess指定访问权限
1 2 3 |
HANDLE OpenSemaphore(DWORD dwDesiredAccess, BOOL bInheritHandle, PCTSTR pszName); |
1 2 3 4 |
// 递增信号量的当前资源计数 BOOL ReleaseSemaphore(HANDLE hSemaphore, LONG lReleaseCount, PLONG plPreviousCount); |
- 把lReleaseCount的值加到当前资源计数上
- plPreviousCount存的是当前资源计数的原始值
互斥量内核对象
概述
- 互斥量内核对象用来确保一个线程独占对一个资源的访问。
- 一般用来对多个线程访问的同一块内存进行保护。因为互斥量可以确保正在访问内存块的任何线程会独占 对内存块的访问权,维护了数据的完整性。
内容
- 一个使用计数
- 一个线程ID
- 用来表示当前占用这个互斥量的线程是系统中的哪个线程
- 一个递归计数
- 表示这个线程占用该互斥量的次数
比较
- 互斥量与关键段的行为完全相同。不一样的是,互斥量是内核对象,而关键段是用户模式下的同步对象。
- 一般情况下,互斥量都要比关键段慢。
- 不同进程中的线程可以访问同一个互斥量,这还意味着线程可以在等待对资源的访问权时指定一个最长等待时间。
规则
- 如果线程ID为0(无效线程ID),那么该互斥量不为任何线程所占用,它处于触发状态。
- 如果线程ID为非零值,那么有一个线程已经占用了该互斥量,它处于为触发状态。
- 于其他所有内核对象不同,操作系统对互斥量进行了特殊处理允许它们违反一些常规的规则:
- 假如线程试图等待一个未触发的互斥量对象。在这种情况下,通常线程会进入等待状态。但是,系统会检查想要获得互斥量的线程的线程ID与互斥量内部记录的线程ID是否相同。如果ID是一致的,那么系统会让线程保持可调度状态(即使给互斥量对象未触发)。
(这一点,对系统内的其他互斥量对象来说,都找不到这种“异常”的举动。)
- 假如线程试图等待一个未触发的互斥量对象。在这种情况下,通常线程会进入等待状态。但是,系统会检查想要获得互斥量的线程的线程ID与互斥量内部记录的线程ID是否相同。如果ID是一致的,那么系统会让线程保持可调度状态(即使给互斥量对象未触发)。
函数
1 2 3 |
HANDLE CreateMutex(PSECURITY_ATTRIBUTES psa, BOOL bInitialOwner, PCTSTR pszName); |
- 第二个参数
- 0表示FALSE
- CREATE_MUTEX_INITIAL_OWNER等价于TRUE
1 2 3 4 |
HANDLE CreateMutexEx(PSECURITY_ATTRIBUTES psa, PCTSTR pszName, DWORD dwFlags, DWORD dwDesiredAccess); |
- 第三个参数用dwFlags代替了bInitialOwner。
1 2 3 |
HANDLE OpenMutex(DWORD dwDesiredAccess, BOOL bInheritHandle, PCTSTR pszName); |
- 第二个参数用来控制互斥量的初始状态
- 如果传的是FALSE,互斥量对象的线程ID和递归计数都将被设为0。
这意味着互斥量不为任何线程占有,因此处于触发状态。 - 如果传的是TRUE, 那么对象的线程ID将被设为调用线程的线程ID,递归计数将被设为1。
由于线程ID为非零值,因此互斥量最初处于未触发状态。
- 如果传的是FALSE,互斥量对象的线程ID和递归计数都将被设为0。
1 |
BOOL ReleaseMutex(HANDLE hMutex); |
详细
- 为了获得对保护资源的访问权,线程要调用一个等待函数并传入互斥量的句柄。
- 在内部,等待函数会检查线程ID是否为0,为0意味着触发状态。如果为0,那么函数会把线程ID设为调用者的线程ID把递归计数设为1,然后让调用线程继续执行。
- 如果等到的线程ID不为0,意思是互斥量处于未触发状态。那么调用线程将进入等待状态。
- 当领域给线程将互斥量的线程ID设为0的时候,系统会记得有一个线程正在等待,于是它将线程ID设为正在等待的哪个线程的线程ID。并把递归计数设为1。使正在等待的线程变为可调度状态。
- 每次线程成功的等待了一个互斥量,互斥量对象的递归计数会递增。
- 一旦成功的等到了互斥量,线程就知道自己已经独占了对受保护的资源的访问。任何想要试图获得对资源的访问权限的线程都将进入等待状态。
- 当目前占有访问权的线程不再需要访问资源的时候,它必须调用ReleaseMutex函数来释放互斥量。
- 这个函数会将对象的递归计数减1。
- 如果成功等待了互斥量多次,那么线程就必须调用相同次数的ReleaseMutex函数才能使对象的递归计数变成0。
- 当递归计数变成0的时候,函数还会将线程ID设为0,这样,就触发了对象。
- 当对象被触发的时候,系统会检查有没有其他线程正在等待该互斥量
- 如果有,那么系统会“公平地”选择一个正在等待的线程,把互斥量的所有权给它。这也意味着会把对象内部的线程ID设为所选择的那个线程的线程ID,并把递归计数设为1。
- 如果没有线程在等待互斥量,那么该互斥量会保持在触发状态,这样下一个等待它的线程就可以立即得到它。
本文为原创文章,版权归Aet所有,欢迎分享本文,转载请保留出处!
你可能也喜欢
- ♥ 51CTO:C++网络通信引擎架构与实现一09/09
- ♥ Soui一03/17
- ♥ Windows 核心编程 _ 创建&&终止线程07/02
- ♥ Soui八06/20
- ♥ Windows 核心编程 _ 用户模式:线程同步一07/15
- ♥ Windows 核心编程 _ 进程四06/25