線程同步 等待定時器 WaitableTimer 內核對象 CreateWaitableTimer

0、思考

線程a負責發紅包,線程b、c負責搶紅包,線程a不會主動告訴b要發紅包了,那線程b有什麼策略?

1、相關api

CreateWaitableTimer
SetWaitableTimer
CancelWaitableTimer
OpenWaitableTimer
WaitForSingleObject
WaitForMultipleObject
CloseHandle
GetLocalTime
GetSystemTime
SystemTimeToFileTime
LocalFileTimeToFileTime

2、寫在前面

等待定時器:在某個指定的時間觸發或每隔一段時間觸發一次。

3、api說明

// 創建可等待的定時器(在某個時間或間隔時間發出信號通知的內核對象)
// lpTimerAttributes:安全屬性。(通常爲NULL)
// bManualReset:手動重置(true),被觸發時正在等待的所有線程變成可調度狀態;自動重置(false),被觸發時只有一個正在等待該計時器的線程
    變成可調度線程。(通常爲false)
// lpTimerName:內核對象名稱。
WINBASEAPI
__out_opt
HANDLE
WINAPI
CreateWaitableTimerA(
    __in_opt LPSECURITY_ATTRIBUTES lpTimerAttributes,
    __in     BOOL bManualReset,
    __in_opt LPCSTR lpTimerName
);
WINBASEAPI
__out_opt
HANDLE
WINAPI
CreateWaitableTimerW(
    __in_opt LPSECURITY_ATTRIBUTES lpTimerAttributes,
    __in     BOOL bManualReset,
    __in_opt LPCWSTR lpTimerName
);
#ifdef UNICODE
#define CreateWaitableTimer  CreateWaitableTimerW
#else
#define CreateWaitableTimer  CreateWaitableTimerA
#endif // !UNICODE

// 設置觸發定時器
// hTimer:要觸發的定時器句柄
// lpDueTime:第一次觸發的時間
// lPeriod:間隔多久觸發
// pfnCompletionRoutine:(通常爲NULL)
// lpArgToCompletionRoutine:(通常爲NULL)
// fResume:用於支持掛起和繼續執行的計算機(如果希望計算機處於休眠狀態時定時器可以喚醒計算機恢復運行則設置爲true)。(通常爲false)
WINBASEAPI
BOOL
WINAPI
SetWaitableTimer(
    __in     HANDLE hTimer,
    __in     const LARGE_INTEGER *lpDueTime,
    __in     LONG lPeriod,
    __in_opt PTIMERAPCROUTINE pfnCompletionRoutine,
    __in_opt LPVOID lpArgToCompletionRoutine,
    __in     BOOL fResume
);

// 取出定時器的句柄並將它撤消,定時器不會再進行報時(SetWaitableTier前系統會默認CancelWaitableTimer撤銷原報時條件)
// hTimer:定時器
WINBASEAPI
BOOL
WINAPI
CancelWaitableTimer(
    __in HANDLE hTimer
);

// 進程可以獲得它自己的與進程相關的現有等待定時器的句柄
// dwDesiredAccess:指定想要的訪問權限,EVENT_ALL_ACCESS請求對事件對象的完全訪問;EVENT_MODIFY_STATE允許使用。(通常爲EVENT_ALL_ACCESS)
// bInheritHandle:是否希望子進程繼承事件對象的句柄。(通常爲false)
// lpTimerName:要打開的事件對象的名稱。
WINBASEAPI
__out_opt
HANDLE
WINAPI
OpenWaitableTimerA(
    __in DWORD dwDesiredAccess,
    __in BOOL bInheritHandle,
    __in LPCSTR lpTimerName
);
WINBASEAPI
__out_opt
HANDLE
WINAPI
OpenWaitableTimerW(
    __in DWORD dwDesiredAccess,
    __in BOOL bInheritHandle,
    __in LPCWSTR lpTimerName
);
#ifdef UNICODE
#define OpenWaitableTimer  OpenWaitableTimerW
#else
#define OpenWaitableTimer  OpenWaitableTimerA
#endif // !UNICODE

4、C++封裝

#pragma once

#include <windows.h>


class ncWaitableTimer
{
public:
    ncWaitableTimer(BOOL bManualReset = FALSE, LPCTSTR lpTimerName = NULL)
    {
        _timer = CreateWaitableTimer(NULL, bManualReset, lpTimerName);
    }

    ~ncWaitableTimer()
    {
        CloseHandle(_timer);
    }

public:
    BOOL set(const LARGE_INTEGER* lpDueTime, LONG lPeriod, PTIMERAPCROUTINE pfnCompletionRoutine = NULL, LPVOID lpArgToCompletionRoutine = NULL, BOOL fResume = FALSE)
    {
        return SetWaitableTimer(_timer, lpDueTime, lPeriod, pfnCompletionRoutine, lpArgToCompletionRoutine, fResume);
    }

    DWORD wait(DWORD timeout = INFINITE)
    {
        return WaitForSingleObject (_timer, timeout);
    }

    BOOL cancel ()
    {
        return CancelWaitableTimer(_timer);
    }

private:
    HANDLE _timer;
};

5、順藤摸瓜

APC:asynchronous procedure call異步任務調用;
SetWaitableTimer的pfnCompletionRoutine和lpArgToCompletionRoutine用來支持APC;
如果希望時間一到就讓計時器把一個APC添加到隊列中,需要定義一個APC函數,並把函數地址pfnCompletionRoutine傳進來,原形如下:
typedef
VOID
(APIENTRY *PTIMERAPCROUTINE)(
    __in_opt LPVOID lpArgToCompletionRoutine,
    __in     DWORD dwTimerLowValue,
    __in     DWORD dwTimerHighValue
);
注意:計時器被觸發時當且僅當SetWaitableTimer的調用線程處於可提醒狀態。(例如:SleepEx、WaitForSingleObjectEx、WaitForMultipleObjectsEx、SignalObjectAndWait這些進入等待狀態)
原因:如果線程並非在其中一個函數內等待,那麼系統不會吧計時器的APC函數添加到隊列中。
注意:線程不應該在等待一個計時器句柄的同時以可提醒的方式等待同一個計時器。
原因:當計時器被觸發時,等待成功,線程被喚醒,這時線程退出可提醒狀態,APC函數就不調用了。
注意:WaitForSingleObjectEx實際上會等待計時器兩次:一次是可提醒的,另一次是內核對象句柄;當內核對象句柄被觸發時,線程也退出可提醒狀態;
注意:SleepEx不會等待內核對象句柄,所有線程一直處於可提醒狀態,直到APC函數處理完完畢後返回可警告函數(即退出可提醒狀態)
注意:APC函數得確保再次被觸發之前結束,不然APC函數的加入速度大於處理速度。

鳴謝

覺的我寫的幫幫噠, 發個紅包賞賞賞

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章