C++ CreateThread的使用

函數(shù)原型:

HANDLE
WINAPI
CreateThread(
In_opt LPSECURITY_ATTRIBUTES lpThreadAttributes, {安全設(shè)置}
In SIZE_T dwStackSize, {堆棧大小}
In LPTHREAD_START_ROUTINE lpStartAddress, {入口函數(shù)}
In_opt __drv_aliasesMem LPVOID lpParameter, {函數(shù)參數(shù)}
In DWORD dwCreationFlags, {啟動選項}
Out_opt LPDWORD lpThreadId {輸出線程id}
);

函數(shù)解析

1、返回值:返回線程句柄
"句柄" 類似指針, 但通過指針可讀寫對象, 通過句柄只是使用對象;
有句柄的對象一般都是系統(tǒng)級別的對象(或叫內(nèi)核對象); 之所以給我們的是句柄而不是指針, 目的只有一個: "安全";
貌似通過句柄能做很多事情, 但一般把句柄提交到某個函數(shù)(一般是系統(tǒng)函數(shù))后, 我們也就到此為止很難了解更多了; 事實上是系統(tǒng)并不相信我們.

不管是指針還是句柄, 都不過是內(nèi)存中的一小塊數(shù)據(jù)(一般用結(jié)構(gòu)描述), 微軟并沒有公開句柄的結(jié)構(gòu)細節(jié), 猜一下它應(yīng)該包括: 真實的指針地址、訪問權(quán)限設(shè)置、引用計數(shù)等等.

既然 CreateThread 可以返回一個句柄, 說明線程屬于 "內(nèi)核對象".
實際上不管線程屬于哪個進程, 它們在系統(tǒng)的懷抱中是平等的; 在優(yōu)先級(后面詳談)相同的情況下, 系統(tǒng)會在相同的時間間隔內(nèi)來運行一下每個線程, 不過這個間隔很小很小, 以至于讓我們誤以為程序是在不間斷地運行.

這時你應(yīng)該有一個疑問: 系統(tǒng)在去執(zhí)行其他線程的時候, 是怎么記住前一個線程的數(shù)據(jù)狀態(tài)的?
有這樣一個結(jié)構(gòu) TContext, 它基本上是一個 CPU 寄存器的集合, 線程是數(shù)據(jù)就是通過這個結(jié)構(gòu)切換的, 我們也可以通過 GetThreadContext 函數(shù)讀取寄存器看看.
附上這個結(jié)構(gòu) TContext(或叫: CONTEXT、_CONTEXT) 的定義:

PContext = ^TContext;
_CONTEXT = record
ContextFlags: DWORD;
Dr0: DWORD;
Dr1: DWORD;
Dr2: DWORD;
Dr3: DWORD;
Dr6: DWORD;
Dr7: DWORD;
FloatSave: TFloatingSaveArea;
SegGs: DWORD;
SegFs: DWORD;
SegEs: DWORD;
SegDs: DWORD;
Edi: DWORD;
Esi: DWORD;
Ebx: DWORD;
Edx: DWORD;
Ecx: DWORD;
Eax: DWORD;
Ebp: DWORD;
Eip: DWORD;
SegCs: DWORD;
EFlags: DWORD;
Esp: DWORD;
SegSs: DWORD;
end;

2、參數(shù)6:輸出線程ID
CreateThread 的最后一個參數(shù)是 "線程的 ID";
既然可以返回句柄, 為什么還要輸出這個 ID? 現(xiàn)在我知道的是:
1、線程的 ID 是唯一的; 而句柄可能不只一個, 譬如可以用 GetCurrentThread 獲取一個偽句柄、可以用 DuplicateHandle 復制一個句柄等等.
2、ID 比句柄更輕便.
在主線程中 GetCurrentThreadId、MainThreadID獲取的都是主線程的 ID.

MainInstance: Indicates the instance handle for the main executable.
Use MainInstance to obtain the instance handle for the main executable of an application. This is useful in applications that use runtime libraries or packages, when you need the handle for the executable rather than for the library.

3、參數(shù)5:啟動選項
CreateThread 的倒數(shù)第二個參數(shù) dwCreationFlags(啟動選項) 有兩個可選值:
0: 線程建立后立即執(zhí)行入口函數(shù);
CREATE_SUSPENDED: 線程建立后會掛起等待.
ResumeThread 恢復線程的運行; SuspendThread 掛起線程.
這兩個函數(shù)的參數(shù)都是線程句柄, 返回值是執(zhí)行前的掛起計數(shù).
什么是掛起計數(shù)?
SuspendThread 會給這個數(shù) +1; ResumeThread 會給這個數(shù) -1; 但這個數(shù)最小是 0.
當這個數(shù) = 0 時, 線程會運行; > 0 時會掛起.
如果被 SuspendThread 多次, 同樣需要 ResumeThread 多次才能恢復線程的運行.
ResumeThread 和 SuspendThread 分別對應(yīng) TThread 的 Resume 和 Suspend 方法, 很好理解.

4、參數(shù)4:函數(shù)參數(shù)
線程入口函數(shù)的參數(shù)是個無類型指針(Pointer), 用它可以指定任何數(shù)據(jù); 本例是把鼠標點擊窗體的坐標傳遞給線程的入口函數(shù), 每次點擊窗體都會創(chuàng)建一個線程.

5、參數(shù)3:入口函數(shù)指針
到了入口函數(shù)了, 學到這個地方, 我查了一個入口函數(shù)的標準定義, 這個函數(shù)的標準返回值應(yīng)該是 DWORD, 不過這函數(shù)在 Delphi 的 System 單元定義的是: TThreadFunc = function(Parameter: Pointer): Integer; 我以后會盡量使用 DWORD 做入口函數(shù)的返回值.
這個返回值有什么用呢?
等線程退出后, 我們用 GetExitCodeThread 函數(shù)獲取的退出碼就是這個返回值!
如果線程沒有退出, GetExitCodeThread 獲取的退出碼將是一個常量 STILL_ACTIVE (259); 這樣我們就可以通過退出碼來判斷線程是否已退出.
還有一個問題: 前面也提到過, 線程函數(shù)不能是某個類的方法! 假如我們非要線程去執(zhí)行類中的一個方法能否實現(xiàn)呢?
盡管可以用 Addr(類名.方法名) 或 MethodAddress('published 區(qū)的方法名') 獲取類中方法的地址, 但都不能當做線程的入口函數(shù), 原因可能是因為類中的方法的地址是在實例化為對象時動態(tài)分配的.
后來換了個思路, 其實很簡單: 在線程函數(shù)中再調(diào)用方法不就得了, 估計 TThread 也應(yīng)該是這樣.
CreateThread 第三個參數(shù)是函數(shù)指針, 新線程建立后將立即執(zhí)行該函數(shù), 函數(shù)執(zhí)行完畢, 系統(tǒng)將銷毀此線程從而結(jié)束多線程的故事.

6、參數(shù)2:堆棧大小
棧是私有的但堆是公用的

CreateThread 的第二個參數(shù)是分配給線程的堆棧大小.
這首先這可以讓我們知道: 每個線程都有自己獨立的堆棧(也擁有自己的消息隊列).
什么是堆棧? 其實堆是堆、棧是棧, 有時 "棧" 也被叫做 "堆棧".
它們都是進程中的內(nèi)存區(qū)域, 主要是存取方式不同(棧:先進后出; 堆:先進先出);
"棧"(或叫堆棧)適合存取臨時而輕便的變量, 主要用來儲存局部變量; 譬如 for i := 0 to 99 do 中的 i 就只能存于棧中, 你把一個全局的變量用于 for 循環(huán)計數(shù)是不可以的.
現(xiàn)在我們知道了線程有自己的 "棧", 并且在建立線程時可以分配棧的大小.
前面所有的例子中, 這個值都是 0, 這表示使用系統(tǒng)默認的大小, 默認和主線程棧的大小一樣, 如果不夠用會自動增長;
那主線程的棧有多大? 這個值是可以設(shè)定的: Project -> Options -> Delphi Compiler -> Linking(如圖)
棧是私有的但堆是公用的, 如果不同的線程都來使用一個全局變量有點亂套;
為解決這個問題 Delphi 為我們提供了一個類似 var 的 ThreadVar 關(guān)鍵字, 線程在使用 ThreadVar 聲明的全局變量時會在各自的棧中留一個副本, 這樣就解決了沖突. 不過還是盡量使用局部變量, 或者在繼承 TThread 時使用類的成員變量, 因為 ThreadVar 的效率不好, 據(jù)說比局部變量能慢 10 倍.

7、參數(shù)1:安全設(shè)置
CreateThread 的第一個參數(shù) lpThreadAttributes 是指向 TSecurityAttributes 結(jié)構(gòu)的指針, 一般都是置為 nil, 這表示沒有訪問限制; 該結(jié)構(gòu)的定義是:

//TSecurityAttributes(又名: SECURITY_ATTRIBUTES、_SECURITY_ATTRIBUTES)
_SECURITY_ATTRIBUTES = record
nLength: DWORD; {結(jié)構(gòu)大小}
lpSecurityDescriptor: Pointer; {默認 nil; 這是另一個結(jié)構(gòu) TSecurityDescriptor 的指針}
bInheritHandle: BOOL; {默認 False, 表示不可繼承}
end;

//TSecurityDescriptor(又名: SECURITY_DESCRIPTOR、_SECURITY_DESCRIPTOR)
_SECURITY_DESCRIPTOR = record
Revision: Byte;
Sbz1: Byte;
Control: SECURITY_DESCRIPTOR_CONTROL;
Owner: PSID;
Group: PSID;
Sacl: PACL;
Dacl: PACL;
end;

例子:實現(xiàn)線程函數(shù)傳參

typedef struct SParam  
{  
    int No;  
    unsigned short chnlID;  
    unsigned short sessionID;  
}uParam,*sParam;  
DWORD WINAPI  AccountManager(PVOID pParam);  
void main()  
{  
     DWORD dwThreadId;  
     HANDLE     hThrd = NULL;   // thread handle  
  
    SParam sparam;  
    SParam *p;  
  
    sparam.No = 1;  
    sparam.chnlID = 1;  
    sparam.sessionID = 1;  
    p = &sparam;  
  
            hThrd = (HANDLE)CreateThread(NULL,  
                0,  
                AccountManager,  
                p,  
                0,  
               dwThreadId;  
}  
  
DWORD WINAPI  AccountManager(PVOID pParam)  
{  
    sParam sparam;  
    sparam = (sParam)pParam;  
  
    try  
    {  
          /*Run為自己寫的一個方法,Run(int i,unsigned short chnlID,unsigned short sessionID)*/  
          Run(sparam->No,sparam->chnlID,sparam->sessionID);  
    }  
    catch (...)  
    {  
        logger.error("AccountManager(%d): System error./r/n", threadId);  
    }  
      
}  

延伸 WaitForSingleObject msdn

WaitForSingleObject function
msdn的原文:Waits until the specified object is in the signaled state or the time-out interval elapses.
等待,直到指定的對象是在信號狀態(tài)或超時間隔。
To enter an alertable wait state, use the WaitForSingleObjectEx function. To wait for multiple objects, use WaitForMultipleObjects.
進入一個警戒的等待狀態(tài),使用waitforsingleobjectex函數(shù)。等多個對象,使用waitformultipleobjects函數(shù)。

WaitForSingleObject的原型:
當指定的對象處于有信號狀態(tài)或者等待時間結(jié)束的狀態(tài)時,此函數(shù)返回。
DWORD WaitForSingleObject(
HANDLE hHandle,
DWORD dwMilliseconds
);
參數(shù):
hHandle:指定對象或事件的句柄;
dwMilliseconds: 等待時間,以毫妙為單位,當超過等待時間時,此函數(shù)將返回。如果該參數(shù)設(shè)置為0,則該函數(shù)立即返回,如果設(shè)置為INFINITE,則該函數(shù)直到有信號才返回。
返回值:
如果此函數(shù)成功,該函數(shù)的返回之標識了引起該函數(shù)返回的事件。返回值如下:
WAIT_ABANDONED(0x00000080L)
指定的對象是一個互斥對象,該對象沒有被擁有該對象的線程在線程結(jié)束前釋放?;コ鈱ο蟮乃袡?quán)被同意授予調(diào)用該函數(shù)的線程?;コ鈱ο蟊辉O(shè)置成為無信號狀態(tài)。
WAIT_OBJECT_0 (0x00000000L)
指定的對象出有有信號狀態(tài)。
WAIT_TIMEOUT (0x00000102L)
超過等待時間,指定的對象處于無信號狀態(tài)
如果失敗,返回 WAIT_FAILED;

參考: 事件EVENT與waitforsingleobject的使用

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

  • 原文地址:C語言函數(shù)調(diào)用棧(一)C語言函數(shù)調(diào)用棧(二) 0 引言 程序的執(zhí)行過程可看作連續(xù)的函數(shù)調(diào)用。當一個函數(shù)執(zhí)...
    小豬啊嗚閱讀 4,977評論 1 19
  • Lua 5.1 參考手冊 by Roberto Ierusalimschy, Luiz Henrique de F...
    蘇黎九歌閱讀 14,264評論 0 38
  • 一、溫故而知新 1. 內(nèi)存不夠怎么辦 內(nèi)存簡單分配策略的問題地址空間不隔離內(nèi)存使用效率低程序運行的地址不確定 關(guān)于...
    SeanCST閱讀 8,146評論 0 27
  • 守時是一個老生長談的話題,對于遲到這種行為,有人厭惡至極,有人表示理解,也有人滿不在乎(當然這種情況極少)。...
    太陽升起了閱讀 1,438評論 1 1
  • 給子宜的情書7親愛的孩子,今天,繼胡老師后,你們又換了一個泰國回來的王老師,也就是三個月內(nèi)已經(jīng)換了三個老師,問了你...
    Grit888閱讀 185評論 3 6

友情鏈接更多精彩內(nèi)容