windows核心編程_線程_學習筆記

006---線程的基礎知識
1.線程也是由兩個部分組成的:
一個是線程的內核對象,操作系統用它來對線程實施管理。內核對象也是系統用來存放線程統計信息的地方。
另一個是線程堆棧,它用於維護線程在執行代碼時需要的所有函數參數和局部變量

2.注意CreateThread函數是用來創建線程的Windows函數。不過,如果你正在編寫C/C++代碼,決不應該調用CreateThread。相反,應該使用VisualC++運行期庫函數_beginthreadex。如果不使用Microsoft的VisualC++編譯器,你的編譯器供應商有它自己的CreateThred替代函數。不管這個替代函數是什麼,你都必須使用。
本章後面將要介紹_beginthreadex能夠做什麼,它的重要性何在。

3.
HANDLE GetCurrentProcess();//僞句柄
HANDLE GetCurrentThread();//僞句柄
上面這兩個函數都能返回調用線程的進程的僞句柄或線程內核對象的僞句柄。這些函數並不在創建進程的句柄表中創建新句柄。還有,調用這些函數對進程或線程內核對象的使用計數
沒有任何影響。如果調用CloseHandle,將僞句柄作爲參數來傳遞,那麼CloseHandle就會忽略該函數的調用並返回FALSE。當調用一個需要進程句柄或線程句柄的Windows函數時,可以傳遞一個僞句柄,使該函數
執行它對調用進程或線程的操作。
例如,通過調用下面的GetProcessTimes函數,線程可以查詢它的進程的時間使用情況;同樣,通過調用GetThreadTimes函數,線程可以查詢它自己的線程時間.


少數Windows函數允許用進程或線程在系統範圍內獨一無二的ID來標識某個進程或線程。
HANDLE GetCurrentProcessId();//實句柄
HANDLE GetCurrentThreadId();//實句柄
這兩個函數通常不像能夠返回僞句柄的函數那樣有用,但是有的時候用起來還是很方便的。

4.DuplicateHandle函數能夠將僞句柄變成實句柄.
eg.
DWORD WINAPI ParentThread(PVOIDpvParam)
{
 HANDLE hThreadParent;
 DuplicateHandle(GetCurentProcess(),
  GetCurrentThread(),
  GetCurentProcess(),
  &hThreadParent,
  0,
  FALSE,
  DUPLICATE_SAME_ACCESS);
 CreateThread(NULL,0,ChildThread,(PVOID)hThreadParent,0,NULL);
}

DWORD WINAPI ChildThread(PVOIDpvParam)
{
 HANDLE hThreadParent=(HANDLE)pvParam;
 FILETIME ftCreationTime,ftExitTime,ftKernelTime,ftUserTime;
 GetThreadTimes(hThreadParent,&ftCreationTime,&ftExitTime,&ftKernelTime,&ftUserTime);
 CloseHandle(hThreadParent);
 //...
}
當子線程啓動運行時,它的pvParam參數包含了線程的實句柄。對傳遞該句柄的函數的任何調用都將影響父線程而不是子線程。

由於DuplicateHandle會遞增特定對象的使用計數,因此當完成對複製對象句柄的使用時,應該將目標句柄傳遞給CloseHandle,從而遞減對象的使用計數,這一點很重要。上面的代碼
段已經顯示出這一點。在調用GetThreadTimes之後,緊接着子線程調用CloseHandle,以便遞減父線程對象的使用計數。
。在這個代碼段中,我假設子線程不使用該句柄來調用任何其他函數。如果其他函數被調用,以便傳遞父線程的句柄,那麼在子線程不再需要該句柄之前,不應該調用CloseHandle。


還要指出,DuplicateHandle可以用來將進程的僞句柄轉換成進程的實句柄
HANDLE hProcess;
DuplicateHandle(
 GetCurrentProcess(),
 GetCurrentProcess(),
 GetCurrentProcess(),
 &hProcess,
 0,
 FALSE,
 DUPLICATE_SAME_ACCESS);


007---線程的調度、優先級和親緣性
5.CreateProcess或CreateThread要查看是否已經傳遞了CREATE_SUSPENDED標誌。如果已經傳遞了這個標誌,那麼這些函數就返回,同時新線程處於暫停狀態。如果尚未傳遞該標誌,那麼該函數將線程的暫停計數遞減爲0。當線程的暫停計數是0的時候,除非線程正在等待其他某種事情的發生,否則該線程就處於可調度狀態。當創建線程時,除了使用CREATE_SUSPENDED外,也可以調用SuspendThread函數來暫停線程的運行.該函數返回的是線程的前一個暫停計數,線程暫停的最多次數可以是MAXIMUM_SUSPEND_COUNT次(在WinNT.h中定義爲127)

ResumeThread使線程成爲可調度線程.該函數返回的是線程的前一個暫停計數,

在實際環境中,調用ResumeThread時必須小心,因爲不知道暫停線程運行時它在進行什麼操作。如果線程試圖從堆棧中分配內存,那麼該線程將在該堆棧上設置一個鎖。當其他線程試圖訪問該堆棧時,這些線程的訪問就被停止,直到第一個線程恢復運行。只有確切知道目標線程是什麼(或者目標線程正在做什麼),並且採取強有力的措施來避免因暫停線程的運行而帶來的問題或死鎖狀態,ResumeThread纔是安全的。

6.
對於Windows來說,不存在暫停或恢復進程的概念,因爲進程從來不會被安排獲得CPU時間。但是,曾經有人無數次問我如何暫停進程中的所有線程的運行。Windows確實允許一個進程暫停另一個進程中的所有線程的運行,但是從事暫停操作的進程必須是個調試程序。特別是,進程必須調用WaitForDebugEvent和ContinueDebugEvent之類的函數。由於OpenThread在Windows2000中是個新函數,因此我的SuspendProcess函數在Windows95或Windows98上無法運行,在WindowsNT4.0或更早的版本上也無法運行。也許你懂得爲什麼SuspendProcess不能總是運行,原因是當枚舉線程組時,新線程可以被創建和撤消。因此,當我調用CreateToolhelp32Snapshot後,一個新線程可能會出現在目標進程中,我的函數將無法暫停這個新線程。過了一些時候,當調用SuspendProcess函數來恢復線程的運行時,它將恢復它從未暫停的一個線程的運行。更糟糕的是,當枚舉線程ID時,一個現有的線程可能被撤消,一個新線程可能被創建,這兩個線程可能擁有相同的ID。這將會導致該函數暫停任意些個(也許在目標進程之外的一個進程中的)線程的運行。

7.關於Sleep函數,有下面
幾個重要問題值得注意:
?調用Sleep,可使線程自願放棄它剩餘的時間片。
?系統將在大約的指定毫秒數內使線程不可調度。不錯,如果告訴系統,想睡眠100ms,那麼可以睡眠大約這麼長時間,但是也可能睡眠數秒鐘或者數分鐘。記住,Windows不是個實時操作系統。雖然線程可能在規定的時間被喚醒,但是它能否做到,取決於系統中還有什麼操作正在進行。
?可以調用Sleep,並且爲dwMilliseconds參數傳遞INFINITE。這將告訴系統永遠不要調度該線程。這不是一件值得去做的事情。最好是讓線程退出,並還原它的堆棧和內核對象。
?可以將0傳遞給Sleep。這將告訴系統,調用線程將釋放剩餘的時間片,並迫使系統調度另一個線程。但是,系統可以對剛剛調用Sleep的線程重新調度。如果不存在多個擁有相同優先級的可調度線程,就會出現這種情況。


8.調用SwitchToThread函數與調用Sleep是相似的,並且傳遞給它一個0ms的超時。差別是SwitchToThread允許優先級較低的線程運行。即使低優先級線程迫切需要CPU時間,Sleep也能夠立即對調用線程重新進行調度。

9。
GetThreadTimes的函數返回4個不同的時間值
注意,GetProcessTimes是個類似GetThreadTimes的函數,適用於進程中的所有線程:GetProcessTimes返回的時間適用於某個進程中的所有線程(甚至是已經終止運行的線程)。
例如,返回的內核時間是所有進程的線程在內核代碼中經過的全部時間的總和。
Windows98遺憾的是,GetThreadTimes和GetProcessTimes這兩個函數在Windows98中不起作用。在Windows98中,沒有一個可靠的機制可供應用程序來確定線程或進程已經使用了多少CPU時間。
對於高分辨率的配置文件來說,GetThreadTimes並不完美。Windows確實提供了一些高分辨率性能函數:
BOOL QueryPerformanceFrequency(LARGE_INTEGER* pliFrequency);
BOOL QueryPerformanceCounter(LARGE_INTEGER* pliCount);
使用FileTimeToQuadWord這個函數,可以通過使用下面的代碼確定執行復雜的算法時需要的時間

10.
這個Boot.ini文件是Windows2000安裝時產生的,不過我使用Notepad加上了最後一行代碼。
這行代碼告訴系統,在系統引導時,我只應該使用機器中的一個處理器。/NumProcs=1這個開
關是用來實現這一點的關鍵。我常常發現它對調試非常有用。

 

008用戶方式中線程的同步
11
InterlockedExchangeAdd函數
互鎖函數是如何運行的呢?答案取決於運行的是何種CPU平臺。
對於x86家族的CPU來說,互鎖函數會對總線發出一個硬件信號,防止另一個CPU訪問同一個內存地址。
在Alpha平臺上,互鎖函數能夠執行下列操作:
1)打開CPU中的一個特殊的位標誌,並註明被訪問的內存地址。
2)將內存的值讀入一個寄存器。
3)修改該寄存器。
4)如果CPU中的特殊位標誌是關閉的,則轉入第二步。否則,特殊位標誌仍然是打開的,寄存器的值重新存入內存。
你也許會問,執行第4步時CPU中的特殊位標誌是如何關閉的呢?答案是:如果系統中的另一個CPU試圖修改同一個內存地址,那麼它就能夠關閉CPU的特殊位標誌,從而導致互鎖函數返回第二步。

必須保證傳遞給這些函數的變量地址正確地對齊,否則這些函數就會運行失敗(第1 3章將介紹數據對齊問題)。

對於互鎖函數,需要了解的另一個重要問題是,它們運行的速度極快。調用一個互鎖函數通常會導致執行幾個CPU週期(通常小於50),並且不會從用戶方式轉換爲內核方式(通常這需要執行1000個CPU週期)。

當然,可以使用InterlockedExchangeAdd減去一個值—只要爲第二個參數傳遞一個負值。
InterlockedExchangeAdd將返回在*plAddend中的原始值。
InterlockedExchange和InterlockedExchangePointer能夠以原子操作方式用第二個參數中傳遞的值來取代第一個參數中傳遞的當前值。如果是32位應用程序,兩個函數都能用另一個32位值取代一個32位值。但是,如果是個64位應用程序,那麼InterlockedExchange能夠取代一個32位值,而InterlockedExchangePointer則取代64位值。兩個函數都返回原始值。當實現一個循環鎖時,InterlockedExchange是非常有用的

最好是始終都讓單個線程來訪問數據(函數參數和局部變量是確保做到這一點的最好方法),或者始終讓單個CPU訪問這些數據(使用線程親緣性)。如果採取其中的一種方法,就能夠完全避免高速緩存行的各種問題。

12.
volatile類型的限定詞。它告訴編譯器,變量可以被應用程序本身以外的某個東西進行修改,這些東西包括操作系統,硬件或同時執行的線程等。尤其是,volatile限定詞會告訴編譯器,不要對該變量進行任何優化,並且總是重新加載來自該變量的內存單元的值

13.
關鍵代碼段是指一個小代碼段,在代碼能夠執行前,它必須獨佔對某些共享資源的訪問權。這是讓若干行代碼能夠“以原子操作方式”來使用資源的一種方法。所謂原子操作方式,是指該代碼知道沒有別的線程要訪問該資源。當然,系統仍然能夠抑制你的線程的運行,而搶先安排其他線程的運行。不過,在線程退出關鍵代碼段之前,系統將不給想要訪問相同資源的其他任何線程進行調度。
有一個關鍵問題必須記住。當擁有一項可供多個線程訪問的資源時,應該創建一個CRITICAL_SECTION結構。
注意最難記住的一件事情是,編寫的需要使用共享資源的任何代碼都必須封裝在EnterCriticalSection和LeaveCriticalSection函數中。如果忘記將代碼封裝在一個位置,共享資源就可能遭到破壞。
關鍵代碼段的優點在於它們的使用非常容易,它們在內部使用互鎖函數,這樣它們就能夠迅速運行。關鍵代碼的主要缺點是無法用它們對多個進程中的各個線程進行同步。不過在第1 9章中,我將要創建我自己的同步對象,稱爲Optex。這個對象將顯示操作系統如何來實現關鍵代碼段,它也能用於多個進程中的各個線程。

當線程試圖進入另一個線程擁有的關鍵代碼段時,調用線程就立即被置於等待狀態。這意味着該線程必須從用戶方式轉入內核方式(大約1 0 0 0個C P U週期)。這種轉換是要付出很大代價的。在多處理器計算機上,當前擁有資源的線程可以在不同的處理器上運行,並且能夠很快放棄對資源的控制。實際上擁有資源的線程可以在另一個線程完成轉入內核方式之前釋放資源。如果出現這種情況,就會浪費許多CPU時間。

在內存不足的情況下,關鍵代碼段可能被爭用,同時系統可能無法創建必要的事件內核對
象。這時EnterCriticalSection函數將會產生一個EXCEPTION_INVALID_HANDLE異常。大多
數編程人員忽略了這個潛在的錯誤,在他們的代碼中沒有專門的處理方法,因爲這個錯誤非常
少見。但是,如果想對這種情況有所準備,可以有兩種選擇。


14.若要將循環鎖用於關鍵代碼段,應該調用下面的函數,以便對關鍵代碼段進行初始化
InitializeCriticalSectionAndSpinCount的第一個參數是關鍵代碼段結構的地址。但是在第二個參數dwSpinCount中,傳遞的是在使線程等待之前它試圖獲得資源時想要循環鎖循環迭代的次數。這個值可以是0至0x00FFFFFF之間的任何數字。如果在單處理器計算機上運行時調用該函數,dwSpinCount參數將被忽略,它的計數始終被置爲0。這是對的,因爲在單處理器計算機上設置循環次數是毫無用處的,如果另一個線程正在循環運行,那麼擁有資源的線程就不能放棄它。

通過調用下面的函數,就能改變關鍵代碼段的循環次數
DWORD SetCriticalSectionSpinCount(
PCRITICAL_SECTION pcs,
DWORD dwSpinCount);同樣,如果主計算機只有一個處理器,那麼dwSpinCount的值將被忽略

我認爲,始終都應該將循環鎖用於關鍵代碼段,因爲這樣做有百利而無一害。難就難在確定爲dwSpinCount參數傳遞什麼值。爲了實現最佳的性能,只需要調整這些數字,直到對性能結果滿意爲止。作爲一個指導原則,保護對進程的堆棧進行訪問的關鍵代碼段使用的循環次數是4000次。

15.技巧
1. 每個共享資源使用一個CRITICAL_SECTION變量如果應用程序中擁有若干個互不相干的數據結構,應該爲每個數據結構創建一個CRITICAL_SECTION變量。這比只有單個CRITICAL_SECTION結構來保護對所有共享資源的訪問要好
2. 同時訪問多個資源有時需要同時訪問兩個資源。必須始終按照完全相同的順序請求對資源的訪問
eg.
DWORD WINAPI ThreadFunc(PVOID pvParam)
{
EnterCriticalSection(&g_csNums);
EnterCriticalSection(&g_csChars);

for(int x=0;x<100;x++)
g_nNums[x] = g_cChars[x];

LeaveCriticalSection(&g_csChars);
LeaveCriticalSection(&g_csNums);
return(0);
}

假定下面這個函數的進程中的另一個線程也要求訪問這兩個數組:
//err
DWORD WINAPI OtherThreadFunc(PVOID pvParam)
{
EnterCriticalSection(&g_csChars);
EnterCriticalSection(&g_csNums);

for(int x=0;x<100;x++)
g_nNums[x] = g_cChars[x];

LeaveCriticalSection(&g_csNums);
LeaveCriticalSection(&g_csChars);
return(0);
}
在上面這個函數中我只是切換了對EnterCriticalSection和LeaveCriticalSection函數的調用順序。但是,由於這兩個函數是按上面這種方式編寫的,因此可能產生一個死鎖狀態。假定ThreadFunc開始執行,並且獲得了g_csNums關鍵代碼段的所有權,那麼執行OtherThreadFunc函數的線程就被賦予一定的CPU時間,並可獲得g_csChars關鍵代碼段的所有權。這時就出現了一個死鎖狀態。當ThreadFunc或OtherThreadFunc中的任何一個函數試圖繼續執行時,這兩個函數都無法取得對它需要的另一個關鍵代碼段的所有權。
爲了解決這個問題,必須始終按照完全相同的順序請求對資源的訪問。注意,當調用LeaveCriticalSection函數時,按照什麼順序訪問資源是沒有關係的,因爲該函數決不會使線程進入等待狀態。
//ok
DWORD WINAPI OtherThreadFunc(PVOID pvParam)
{
EnterCriticalSection(&g_csNums);
EnterCriticalSection(&g_csChars);

for(int x=0;x<100;x++)
g_nNums[x] = g_cChars[x];

LeaveCriticalSection(&g_csChars);
LeaveCriticalSection(&g_csNums);
return(0);
}
3.不要長時間運行關鍵代碼段
當一個關鍵代碼段長時間運行時,其他線程就會進入等待狀態,這會降低應用程序的運行性能。

 

009線程與內核對象的同步
16.雖然用戶方式的線程同步機制具有速度快的優點,但是它也有其侷限性。對於許多應用程
序來說,這種機制是不適用的。例如,互鎖函數家族只能在單值上運行,根本無法使線程進入
等待狀態。可以使用關鍵代碼段使線程進入等待狀態,但是隻能用這些代碼段對單個進程中的
線程實施同步。還有,使用關鍵代碼段時,很容易陷入死鎖狀態,因爲在等待進入關鍵代碼段
時無法設定超時值。內核對象機制的適應性
遠遠優於用戶方式機制。實際上,內核對象機制的唯一不足之處是它的速度比較慢。當調用本
章中提到的任何新函數時,調用線程必須從用戶方式轉爲內核方式。這個轉換需要很大的代價:
往返一次需要佔用x86平臺上的大約1000個CPU週期,當然,這還不包括執行內核方式代碼,
即實現線程調用的函數的代碼所需的時間。
SetKMode這個函數用於在兩種模式間切換,改變模式只需修改一些標誌,所以在選擇同步機制上應該優先考慮運行在用戶模式的同步解決辦法。
17.
DWORD dw = WaitForSingleObject(hProcess,5000);
//第一個參數hObject標識一個能夠支持被通知/未通知的內核對象
//INFINITE表示調用線程願意永遠等待下去(無限時間量),直到該進程終止運行已經定義爲0xFFFFFFFF(或-1)
//注意,不能爲dwMillisecond傳遞0.如果傳遞了0.WaitForSingleObject函數將總是立即返回.
switch(dw)
{
case WAIT_OBJECT_0://線程等待的對象變爲已通知狀態
 break;
case WAIT_TIMEOUT://設置的超時已經到期
 break;
case WAIT_FAILED://如果將一個錯誤的值(如一個無效句柄)傳遞給WaitForSingleObject,那麼返回值將是WAIT_FAILED(若要了解詳細信息,可調用GetLastError)
 break;
}
18.
WaitForMultipleObjects與WaitForSingleObject函數很相似,區別在於它允許調用線程同時查看若干個內核對象的已通知狀態

DWORD WaitForMultipleObjects(
DWORD dwCount,   //要查看的內核對象的數量(1~MAXIMUM_WAIT_OBJECTS(在windows頭文件中定義64))
CONST HANDLE * phObjects, //phObjects參數是指向內核對象句柄的數組的指針
BOOL fWaitAll,   //如果爲該參數傳遞TRUE,那麼在所有對象變爲已通知狀態之前,該函數將不允許調用線程運行
DWORD dwMilliseconds);

如果爲fWaitAll參數傳遞TRUE,同時所有對象均變爲已通知狀態,那麼返回值是WAIT_OBJECT_0
如果爲fWaitAll傳遞FALSE,那麼一旦任何一個對象變爲已通知狀態,該函數便返回.在這種情況下,你可能想要知道哪個對象變爲已通知狀態.返回值是WAIT_OBJECT_0與(WAIT_OBJECT_0+dwCount-1)之間的一個值。換句話說,如果返回值不是WAIT_TIMEOUT,也不是WAIT_FAILED,那麼應該從返回值中減去WAIT_OBJECT_0。產生的數字是作爲第二個參數傳遞給WaitForMultipleObjects的句柄數組中的索引。該索引說明哪個對象變爲已通知狀態

19.
HANDLE CreateEvent(
PSECURITY_ATTRIBUTES psa,
BOOL fManualReset, //TRUE:人工重置的事件  FALSE:自動重置的事件
BOOL fInitialState, //TRUE:初始化爲已通知狀態 FALSE:初始化爲未通知狀態
PCTSTR pszName);

DuplicateHandle/OPenEvent  在參數pszName中設定與調用CreateEvent時設定的名字相匹配的名字
當不再需要事件內核對象時,應該調用CloseHandle函數
當調用SetEvent時,可以將事件改爲已通知狀態
當調用ResetEvent函數時,可以將該事件改爲未通知狀態


當這個進程啓動時,它創建一個人工重置的未通知狀態的事件,並且將句柄保存在一個全
局變量中。這使得該進程中的其他線程能夠非常容易地訪問同一個事件對象。現在3個線程已
經產生。這些線程要等待文件的內容讀入內存,然後每個線程都要訪問它的數據。一個線程進
行單詞計數,另一個線程運行拼寫檢查器,第三個線程運行語法檢查器。這3個線程函數的代
碼的開始部分都相同,每個函數都調用WaitForSingleObject,這將使線程暫停運行,直到文件
的內容由主線程讀入內存爲止。
一旦主線程將數據準備好,它就調用SetEvent,給事件發出通知信號。這時,系統就使所
有這3個輔助線程進入可調度狀態,它們都獲得了CPU時間,並且可以訪問內存塊。注意,這3
個線程都以只讀方式訪問內存。這就是所有3個線程能夠同時運行的唯一原因。還要注意,如
何計算機上配有多個CPU,那麼所有3個線程都能夠真正地同時運行,從而可以在很短的時間
內完成大量的操作。

如果你使用自動重置的事件而不是人工重置的事件,那麼應用程序的行爲特性就有很大的
差別。當主線程調用SetEvent之後,系統只允許一個輔助線程變成可調度狀態。同樣,也無法
保證系統將使哪個線程變爲可調度狀態。其餘兩個輔助線程將繼續等待。
已經變爲可調度狀態的線程擁有對內存塊的獨佔訪問權。讓我們重新編寫線程的函數,使
得每個函數在返回前調用SetEvent函數(就像WinMain函數所做的那樣)。
當線程完成它對數據的專門傳遞時,它就調用SetEvent函數,該函數允許系統使得兩個正
在等待的線程中的一個成爲可調度線程。同樣,我們不知道系統將選擇哪個線程作爲可調度線
程,但是該線程將進行它自己的對內存塊的專門傳遞。當該線程完成操作時,它也將調用
SetEvent函數,使第三個即最後一個線程進行它自己的對內存塊的傳遞。注意,當使用自動重
置事件時,如果每個輔助線程均以讀/寫方式訪問內存塊,那麼就不會產生任何問題,這些線
程將不再被要求將數據視爲只讀數據。這個例子清楚地展示出使用人工重置事件與自動重置事
件之間的差別。


PulseEvent函數使得事件變爲已通知狀態,然後立即又變爲未通知狀態,這就像在調用
SetEvent後又立即調用ResetEvent函數一樣。如果在人工重置的事件上調用PulseEvent函數,那
麼在發出該事件時,等待該事件的任何一個線程或所有線程將變爲可調度線程。如果在自動重
置事件上調用PulseEvent函數,那麼只有一個等待該事件的線程變爲可調度線程。如果在發出
事件時沒有任何線程在等待該事件,那麼將不起任何作用。PulseEvent函數並不非常有用。


20.
等待定時器是在某個時間或按規定的間隔時間發出自己的信號通知的內核對象.它們通常
用來在某個時間執行某個操作。
若要創建等待定時器,只需要調用CreateWaitableTimer函數:
HANDLE CreateWaitableTimer(
PSECURITY_ATTRIBUTES psa,
BOOL fManualReset,
PCTSTR pszName);
當然,進程可以獲得它自己的與進程相關的現有等待定時器的句柄,方法是調用OpenWaitableTimer函數.
HANDLE OpenWaitableTimer(
DWORD dwDesiredAccess,
BOOL bInheritHandle,
PCTSTR pszName);

 

等待定時器對象總是在未通知狀態中創建。必須調用SetWaitableTimer函數來告訴定時器你想在何時讓它成爲已通知狀態
BOOL SetWaitableTimer(
HANDLE hTimer,//要設置的定時器
const LARGE_INTEGER *pDueTime,//指明定時器何時應該報時 只需要在pDueTime參數中傳遞一個負值。傳遞的值必須是以100ns爲間隔。由於我們通常並不以100ns的間隔來思考問題,因此我們要說明一下100ns的具體概念:1s=1000ms=1000000μs=1000000000ns。
LONG lPeriod,//用於指明此後定時器應該間隔多長時間報時一次
PTIMERAPCROUTINE pfnCompletionRoutine,
PVOID pvArgToCompletionRoutinue,
BOOL fResume);
SetWaitableTimer希望傳遞給它的時間始終都採用世界協調時(UTC)的時間。調用LocalFileTimeToFileTime函數,就可以很容易地進行時間的轉換。
編譯器能夠確保LARGE_INTEGER結構總是從64位的邊界開始,因此要進行的正確操作(也就是所有時間都能保證起作用的操作)是將FILETIME的成員拷貝到LARGE_INTEGER的成員中,然後將LARGE_INTEGER的地址傳遞給SetWaitableTimer。

CancelWaitableTimer函數...

 

 

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