Windows下的多線程


  Windows下的進程和Linux下的進程是不一樣的,它比較懶惰,從來不執行任何東西,它只是為線程提供執行環境,然后由線程負責執行包含在進程的地址空間中的代碼。當創建一個進程的時候,操作系統會自動創建這個進程的第一個線程,成為主線程。線程由兩部分組成:一是線程的內核對象。操作系統用它來對線程實施管理,內核對象也是系統用來存放線程統計信息的地方。二是線程棧。線程棧用於維護線程在執行代碼時所需的所有函數參數和局部變量。線程可以訪問所在進程的內核對象的所有句柄、所有內存和這個進程中的其他線程的堆棧。

    在Windows下你可以調用Win SDK的API來創建一個線程,也可以用使用C Run-Time Library中的函數來創建,還可以使用MFC封裝的相關線程函數來得到你想要的線程。最近正好閱讀了相關方面的書籍,正好做下總結。

一  WinAPI下的線程

1 創建線程

HANDLE
WINAPI
CreateThread(
    __in_opt  LPSECURITY_ATTRIBUTES lpThreadAttributes,
    __in      SIZE_T dwStackSize,
    __in      LPTHREAD_START_ROUTINE lpStartAddress,
    __in_opt  LPVOID lpParameter,
    __in      DWORD dwCreationFlags,
    __out_opt LPDWORD lpThreadId
    );

 

  • lpThreadAttributes

  指向LPSECURITY_ATTRIBUTES結構體的指針,默認設為NULL,讓線程使用默認的安全性。如果希望所有子線程能夠繼承線程對象的句柄,則必須設定LPSECURITY_ATTRIBUTES結構體,將它的bInHeriHandle初始化未TRUE。

  • dwStackSize

    設置初始棧的大小,默認設為0。

  • lpStartAddress

  指向LPTHREAD_START_ROUTINE指向的函數指針,即為新線程的起始地址。該函數的名稱任意,但是函數類型必須遵照下面聲明的形式:

typedef DWORD (WINAPI *PTHREAD_START_ROUTINE)(
    LPVOID lpThreadParameter
    );

 

  • lpParameter

  新線程調用函數的命令行參數。

  • dwCreationFlags

  用於控制線程創建的附加標記。可以使兩個值之一:CRETE_SUSPENDED或0。如果是SUSPENDED,那么線程創建后將處於暫停狀態。如果是0,線程在創建后立即運行。

  • lpThreadId

  指向一個變量,用來接受線程的ID。如果對線程ID不感興趣可以直接設為NULL。

  注意:CreateThread()函數傳回的值有兩個,第一個值是返回值HANDLE,這個值是線程的HANDLE,是線程所在的進程中的局部變量,在不同的進程中是不唯一的,所以你不能直接跨進程的把一個線程的HANDLE傳給另外一個進程中的線程讓其使用。第二個值是由lpThreadId傳回的線程ID,此值是一個全局變量,可以獨一無二的表示系統中任意進程中的某個線程。比如調用AttachThreadInput()或PostThreadMessage()就需要用線程ID,而不能用線程的HANDLE。

  2  關閉線程句柄

    當完成工作后,應該帶哦用CloseHandle釋放核心對象。

BOOL
WINAPI
CloseHandle(
    __in HANDLE hObject
    );

  如果成功則傳回TRUE,失敗則傳回FALSE。當創建一個線程后立刻調用CloseHandle()函數並不會終止新創建的線程,只是表示在主線程中新創建的線程的引用不感興趣,因此將它關閉。當關閉該線程的句柄時,系統會遞減該線程內核對象的使用計數。線程被創建的時候,線程內核對象的默認引用計數是2,當創建的這個新線程執行完畢后,系統也會遞減該線程內核對象的引用計數。當使用計數為0時,系統會釋放該線程內核對象。如果沒有關閉線程句柄,系統就會一直保持對線程內核對象的引用,這樣即使線程執行完畢,它的引用計數仍然不會為哦,這樣該線程內核對象也就不會被釋放,只有等到進程終止時,系統才會清理這些殘留的對象。因此在程序中,當不再需要線程的句柄時,應將其關閉。

3 線程結束代碼

BOOL
WINAPI
GetExitCodeThread(
    __in  HANDLE hThread,
    __out LPDWORD lpExitCode
    );

  如果成功,GetExitCodeThread()傳回TRUE,否則傳回FALSE。GetExitCodeThread()將傳回線程函數的返回值,但是當線程還在進行尚未結束代碼時,它會傳回TRUE表示成功,此時lpExitCode指向的內存區域存放的是STILL_ACTIVE。

  4 結束線程

  當線程函數結束的時候,線程也就結束了,但是還可以用更暴力的手段讓線程結束,就是調用線程結束函數ExitThread():

VOID
WINAPI
ExitThread(
    __in DWORD dwExitCode
    );

此函數可以在任何時候被調用而絕不會返回,任何在它之后的代碼,都不會被調用。

還可以調用函數TerminateThread函數來殺死一個線程,不同於ExitThread總是殺死主調線程,TerminateThread可以殺死任何線程。

VOID
WINAPI
ExitThread(
    HANDLE hThread,
DWORD dwExitCode
);

示例程序1:創建5個線程,此5個線程的調用函數打印出當前線程正在運行,然后返回。同時,我們另外創立一個監控監控線程,來監控這個5個線程的狀態。


#include <windows.h> #include <process.h> #include <iostream> #include <stdio.h> #include <string.h> #include <tchar.h> #define THREAD_NUM 5 DWORD WINAPI ThreadFunc(LPVOID); DWORD WINAPI MonitorFunc(LPVOID); void ExitFunc(void); int main( ) { HANDLE hThread[THREAD_NUM]; for (int i = 0; i < THREAD_NUM; ++i) { hThread[i] = CreateThread(NULL, 0, ThreadFunc,(LPVOID)i, 0, NULL); } HANDLE monitorThread; monitorThread = CreateThread(NULL, 0, MonitorFunc, (LPVOID)hThread, 0, NULL); if (WaitForSingleObject(monitorThread, INFINITE)) CloseHandle(monitorThread); for (int i = 0; i < THREAD_NUM; ++i) { if (WaitForSingleObject(hThread[i], INFINITE)) { CloseHandle(hThread[i]); } } cout<<"所有線程已經運行結束!"<<endl; system("pause"); return EXIT_SUCCESS; } DWORD WINAPI ThreadFunc(LPVOID lpPara) { cout<<"線程"<< (int)(lpPara)<<"正在運行"<<endl; Sleep(10); return 0; } DWORD WINAPI MonitorFunc(LPVOID hThread) { DWORD exitCoed; int exitThreadCount = 0; int threadStatus[THREAD_NUM]; memset(threadStatus, 0, THREAD_NUM); while(TRUE) { for (int i = 0; i < THREAD_NUM; ++i) { GetExitCodeThread( ((HANDLE*)hThread)[i], &exitCoed); if (exitCoed == STILL_ACTIVE) cout<<"根據ExitCode判斷:線程"<<i<<"仍然在運行!"<<endl; else { cout<<"根據ExitCode判斷:線程"<<i<<"已經結束運行"<<endl; threadStatus[i] = 1; } } exitThreadCount = 0; for (int i = 0; i < THREAD_NUM; ++i) exitThreadCount += threadStatus[i]; if (exitThreadCount == THREAD_NUM) break; } return 0; }

 

二  C Run-time Library 下的線程

   在《Win32 多線程程序設計》一書中說道:“在微軟的Programing Techniques說明文件中有一句看似悲慘的警告”:

   警告:如果你在一個與LIBCMT.LIB鏈接的程序中調用C runtime函數,你的線程就必須以_beginthread()啟動之,不要使用Win32的ExitThread()和ExitThread()。這

  這句警告是說_beginthread有一個沖突狀態,不可完全信賴它。所以在C Runtime Library中應該用_beiginthreadex()和_endthreadex()。主要原因是C Runtime libray是上個世紀70年代產生出來的,那個時候多任務還是新奇概念,所以更不知道多線程是什么東東了。這導致C Runtime libray中的使用的數個全局變量和靜態變量可能在多線程程序中彼此引起沖突,比如errno。還有一點,_beginthread產生出來的線程所做的第一件事情就是關閉自己的handle。所以現在Visual C++提供了兩個版本的C Runtime libray,一個版本供單線程程序使用,一個供多線程程序使用。第三部分介紹的MFC程序必須使用多線程的C Runtime libray,否則在鏈接時會出現錯誤。

  1 線程的創建

uintptr_t __cdecl _beginthreadex(_In_opt_ void * _Security,
                                 _In_ unsigned _StackSize,
                                 _In_ unsigned (__stdcall * _StartAddress) (void *),
                                 _In_opt_ void * _ArgList, 
                                 _In_ unsigned _InitFlag,
                                 _In_opt_ unsigned * _ThrdAddr);

  參數說明:

  • _Security 

  相當於CreateThread()中的安全屬性參數,默認設置為NULL。對應Win32數據類型是LPSECURITY_ATTRIBUTES。

  • _StackSize

新線程的堆棧大小,單位是字節(byte)。對應Win32數據類型是DWORD

  • _StartAddress

  線程啟動函數。

  • _ArgList

  新線程將接收到一個指針,這個指針指示單純的被傳過去,運行庫並沒有對它做拷貝操作。

  • _InitFlag

  啟動時的狀態標記。

  • _ThrdAddr

新線程的ID通過此參數傳回。

_beginthreadex函數的返回值為handle,這個值必須被強制轉換為Win32的HANDLE之后才能被使用。如果函數失敗,則傳回0,而其原因被設置在errno和doserrno全局變量中。

 

2 關閉線程句柄

  在C Runtime Libray下的_beiginthreadex()創建線程內部調用的就是WinAPI的CreateThread()函數。這也是很讓人蛋疼的地方,微軟想讓C Runtime Libray跨平台,但是卻使用的WinAPI的函數,想法是好的,只是沒有去實現。正是這樣子,所以_beiginthreadex()的返回值handle就是WinAPI中的HANDLE,只是需要強制轉換下才能使用。所以,在C Runtime Libray下關閉一個線程的句柄同樣是調用函數CloseHandle()。

 

3 結束一個線程

  在C Runtime Libray下於ExitThread()相對應的函數是_endthreadex():

 void __cdecl _endthreadex(_In_ unsigned _Retval);

  和ExitThread()函數一樣,_endthreadex()可以被線程在任意時間使用,它需要表示一個“線程返回代碼”的參數。但是絕對不要再一個以_beiginthreadex()啟動的線程中調用ExitThrad(),因為這樣C Runtime Libray就沒有機會釋放該線程的資源了。

  為了適當的清除C Runtime Libray中的結構,對於以_beiginthreadex()來產生新線程的程序,應該使用下面兩種方法來結束程序:

  1. 調用C Runtime Libray中的exit()函數。
  2. 從main()返回系統。

  任何一種情況下,runtime libray都會自動進行清理操作,組后調用ExitProcess()。使用任一種技術都不會等待線程的結束,。

 

三 MFC中的線程

  如果要在MFC程序中產生一個線程,而該線程將調用MFC函數或使用MFC的任何數據,那么必須以AfxBeginThread()或CWinThread::CreateThrad()來產生這些線程。 

 

 

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM