【線程】
創建線程的函數是CreateThread,其原型如下:
HANDLE WINAPI CreateThread
(
LPSECURITY_ATTRIBUTES lpThreadAttributes,
SIZE_T dwStackSize,
LPTHREAD_START_ROUTINE lpStartAddress,
LPVOID lpParameter,
DWord dwCreationFlags,
LPDWord lpThreadId
);
參數說明:
- 第一個參數關於安全性,通常值為NULL。Windows會設置為默認安全級別。
- 第二個參數關於堆棧大小,(關於線程的堆棧,以後有機會一定仔細學習)對於簡單應用,同安全性相同,使用默認的大小即可,一般傳入0。
- 第三個參數是線程函數名,用於指明想要新線程執行的線程函數的地址。線程函數可以使用任何名字。實際上,如果在應用程序中擁有多個線程函數,必須為它們賦予不同的名字,否則編譯器/鏈接程序會認為你為單個函數創建了多個實現函數。
- 第四個參數是線程函數的傳入參數,在線程啟動執行時將該參數傳遞給線程函數。該參數提供了一個將初始化值傳遞給線程函數的手段。該初始化數據既可以是數字值,也可以是指向包含其他信息的一個數據結構的指針。
- 第五個參數是設定用於控制創建線程的其他標志。它可以是兩個值中的一個。如果該值是0,那麼線程創建後可以立即進行調度。如果該值是CREATE_SUSPENDED,系統可以完整地創建線程並對它進行初始化,但是要暫停該線程的運行,這樣它就無法進行調度。
- 第六個參數是線程的ID,當傳入NULL時,表示並不關心線程的ID。
- 返回值:線程函數必須返回一個值,它將成為該線程的退出代碼。
線程函數(實際上是你的所有函數)應該盡可能使用函數參數和局部變量。當使用靜態變量和全局變量時,多個線程可以同時訪問這些變量,這可能破壞變量的內容。然而,參數和局部變量是在線程堆棧中創建的,因此它們不太可能被另一個線程破壞。
在實際coding中,根據線程的作用,可以把線程封裝在一個類裡,如果需要讓線程唯一,那麼線程函數應該為static的;如果想讓線程不唯一,那麼就需要以成員函數的形式傳入。不論上述兩種情況的哪一種,都可以讓第四個參數傳入this,這樣做比較方便。
如果線程能夠返回,就可以確保下列事項的實現:
- 在線程函數中創建的所有C + +對象均將通過它們的撤消函數正確地撤消。
- 操作系統將正確地釋放線程堆棧使用的內存。
- 系統將線程的退出代碼(在線程的內核對象中維護)設置為線程函數的返回值。
- 系統將遞減線程內核對象的使用計數。
ExitThread和TerminateThread都可以作為線程退出的函數,不過區別明顯:
ExitThread函數將終止線程的運行,並導致操作系統清除該線程使用的所有操作系統資源。但是,C++資源(如C++類對象)將不被撤消。
TerminateThread函數終止線程時,會造成如下結果:不會對關鍵代碼段對象進行操作,也就是說,如果線程中存在關鍵代碼段,那他不會被銷毀。分配的資源也不會從堆中撤銷。線程狀態被設置成inconsistent。當線程終止運行時,DLL通常接收通知。但使用TerminateThread 強迫線程終止,DLL就不接收通知。
【關鍵代碼段】
CRITICAL_SECTION變量被用於控制多線程訪問共享區的互斥行為。
在使用關鍵代碼段之前,要對CRITICAL_SECTION進行初始化,調用Initialize函數,需要使用互斥時調用Enter函數,結束互斥行為時使用Leave函數,銷毀變量時使用Delete函數,原型如下:
void InitializeCriticalSection(
LPCRITICAL_SECTION lpCriticalSection
);
void EnterCriticalSection(
LPCRITICAL_SECTION lpCriticalSection
);
void LeaveCriticalSection(
LPCRITICAL_SECTION lpCriticalSection
);
void DeleteCriticalSection(
LPCRITICAL_SECTION lpCriticalSection
); EnterCriticalSection函數負責查看該結構中的成員變量。這些變量用於指明當前是哪個變量正在訪問該資源。如果成員變量指明,調用線程已經被賦予對資源的訪問權,那麼EnterCriticalSection便更新這些變量,以指明調用線程多少次被賦予訪問權並立即返回,使該線程能夠繼續運行。
如果成員變量指明,一個線程(除了調用線程之外)已被賦予對資源的訪問權,那麼EnterCriticalSection將調用線程置於等待狀態。這種等待的線程不會浪費任何CPU時間。系統能夠記住該線程想要訪問該資源並且自動更新CRITICAL_SECTION的成員變量,一旦目前訪問該資源的線程調用LeaveCriticalSection函數,該線程就處於可調度狀態。
LeaveCriticalSection要查看該結構中的成員變量。該函數每次計數時要遞減1,以指明調用線程多少次被賦予對共享資源的訪問權。如果該計數大於0,那麼LeaveCriticalSection不做其他
任何操作,只是返回而已。如果該計數變為0,它就要查看在調用EnterCriticalSection中是否有別的線程正在等待。如果至少有一個線程正在等待,它就更新成員變量,並使等待線程中的一個線程(“公正地”選定)再次處於可調度狀態。如果沒有線程正在等待,LeaveCriticalSection函數就更新成員變量,以指明沒有線程正在訪問該資源。
如果不想等,想得到要麼能夠使用資源,要麼去做別的事情的方法,就用TryEnterCriticalSection,當能夠使用資源時,該函數返回TRUE,不能時返回FALSE。他不會把自己加入等待隊列中去。
使用關鍵代碼段時有一些技巧:
- 每個共享資源使用一個CRITICAL_SECTION變量,如果應用程序中擁有若干個互不相干的數據結構,應該為每個數據結構創建一個CRITICAL_SECTION變量。這比只有單個CRITICAL_SECTION結構來保護對所有共享資源的訪問要好(效率高)。
- 當同時訪問多個資源時,必須始終按照完全相同的順序請求對資源的訪問。
- 不要長時間運行關鍵代碼段,當一個關鍵代碼段長時間運行時,其他線程就會進入等待狀態,這會降低應用程序的運行性能。