線程一

        線程由兩部分組成:線程內核對象和線程堆棧。

        每個進程至少有一個線程。當進程初始化時,系統就會創建一個主線程。該主線程隨C/C++運行期啓動代碼一道運行,啓動代碼會調用進入點函數(main、wmain、WinMain或wWinMain),知道進入點函數返回,啓動代碼會調用ExitProcess結束進程爲止。

        你可以在主線程中創建輔助線程,這些輔助線程必須要有自己的進入點函數,其形式如下:

       DWORD WINAPI ThreadFunctionName(PVOID pvPrame){};

       在這個進入點函數中,你可以做想做的事情。當線程函數運行到結尾處時並返回,線程結束運行,釋放線程堆棧,線程內核對象引用計數減1,如果線程內核引用計數爲0,系統撤銷該線程內核對象。

  • 主線程的進入點函數爲main、wmain、WinMain或wWinMain;輔助線程的進入點函數爲我們按上面的方式創建的函數。
  • 進入點函數必須要有一個返回值,該返回值會作爲線程的退出代碼。主線程會將該退出代碼作爲進程的退出代碼   

一、CreateThread函數

HANDLE CreateThread(
    PSECURITY_ATTRIBUTES psa,
    DWORD cbStack,
    PTHREAD_START_ROUTINE pfnStartAddr,
    PVOID pvParam,
    DWORD fdwCreate,
    PDWORD pdwThreadID);

        調用CreateThread創建線程時,系統會創建一個線程內核對象,並且會在進程的地址空間中分配一個內存,作爲線程堆棧使用。

  •         注意:如果Visual c++編譯器時,我們應該使用_beginthreadex函數來替代CreateThread函數,如果是其他編譯器,也應該用其他的CreateThread替代函數。
    psa:用於設定線程內核對象的安全屬性。
  • cbStack:用於設定線程可以將多少地址空間作爲它自己的堆棧。
  • pfnStartAddr:線程的進入點函數,即線程函數。
  • pvPararam:想傳遞給線程進入點函數的指針。
  • fdwCreate:用於設定創建的線程的標誌。如果爲0,表示線程創建後立即執行;爲CREATE_SUSPENDED,表示線程創建後,將線程掛起。
  • pdwThreadID:用於存放系統分配給線程的ID。

二、終止線程運行

終止線程可以使用下面方法:

1)線程函數返回。

2)調用ExitThread函數,線程將自行撤銷。

3)同一個進程或另一個進程調用TerminateThread函數。

4)包含線程的進程終止運行。

方法一、線程函數返回

       這種方法是線程資源被正確釋放的唯一方法。線程返回,能夠確保以下事情:

  • 線程函數中定義的所有C++對象都能夠通過它們的撤銷函數正確地撤銷;
  • 操作系統將正確的釋放線程堆棧使用的內存;
  • 系統將線程的退出代碼設置爲線程函數的返回值;
  • 系統將遞減線程內核對象的引用計數。

方法二、調用ExitThread函數

        這種方法強制線程終止線程運行。這種方法將導致操作系統清除該線程使用的所有操作系統資源。但是C++資源不會被釋放

       VOID ExitThread(DWORD dwExitCode);

       其中dwExitCode參數用於告訴系統將線程的退出代碼設置爲什麼。
方法三、調用TerminateThread函數

       BOOL TerminateThread(HANDLE hThread,DWORD dwExitCode);

        這種方法在擁有線程的進程結束前,不會釋放該線程的堆棧。

方法四、結束擁有線程的進程

線程終止時都會發生的事情:

  • 線程擁有的所有用戶對象均被釋放;
  • 線程退出代碼從STILL_ATIVE該爲傳遞給ExitThread或TerminateThread函數的代碼;
  • 線程內核對象變爲已通知狀態;
  • 如果該線程時最後一個活動線程,那麼進程也被視爲已經終止運行;
  • 線程內核對象引用計數減1.

        可以通過函數GetExitCodeThread來獲取線程退出代碼:

        BOOL GetExitCodehread(HANDLE hThread,PDWORD pdwExitCode);

三、線程的一些性質

         線程內核對象的初始引用計數爲2,暫停計數爲1,退出代碼爲STILL_ACTIVE。

sd

         當線程內核對象創建完成後,系統會爲線程分配堆棧內存,並且首先將傳給線程函數的參數壓入堆棧中,然後將線程函數的地址也壓入堆棧中。每個線程都有自己的上下文(一組寄存器,寄存器的值保持在CONTEXT結構中,CONTEXT包含在線程內核對象中)。線程初始化後,CONTEXT結構的堆棧指針指向堆棧的pfnStartAddrj,指令指針指向BastThreadStart函數的地址。

        線程初始化完成後,會檢查CreateThread函數是否設置了CREATE_SUSPENDED標誌,如果沒有傳遞,那麼會將暫停計數減1,暫停計數爲0後線程被調度在進程中。然後將CONTEXT結構中的值加載到實際的CPU寄存器中,這樣就能夠執行該線程了。對於新線程來說,其指令指針被置爲BaseThreadStart的地址,因此這個函數實際上是新線程執行的開始地方。它有兩個參數,這兩個參數由系統將其壓入堆棧(或者CPU寄存器,看參數的保存形式)中,而不是函數調用。

sd

  

       新線程執行BaseThreadStart函數時,發生的情況:

  • 在線程函數中建立一個結構化異常處理幀,對系統的異常設置默認處理;
  • 調用線程函數,會將參數傳遞個線程函數;
  • 線程函數返回時,BaseThreadStart會調用ExitThread函數,並將線程函數的返回值傳遞給它,線程內核對象引用計數減1;
  • 如果線程出現異常,就會由BaseThreadStart建立的結構化異常處理幀處理異常。

       對於主線程來說,它初始化後指令指針被設置爲另一個未文檔化的函數BaseProcessStart。其形式如下:

sd

sd

        BaseProcessStart沒有引用pvParam參數,當BaseProcessStart開始執行時,其會調用C/C++運行期啓動代碼,啓動代碼會初始化main、wmain、WinMain或wWinMain,然後調用它們。當這些進入點函數返回時,C/C++運行期啓動代碼會調用ExitProcess。

四、C/C++運行期庫
Visuale C++中提供了6個C/C+=運行期庫,其如下:

sd

        爲什麼要多線程版本的運行庫,那是因爲對於單線程版本的運行期庫來說,如果設計多線程應用程序,那麼可能夠造成單線程版本的運行庫中的變量和函數造成同步問題。因此在多線程版本的運行期庫中設計了一個tiddata結構來解決問題。在多線程版本的運行庫中應該使用_ beginthreadex來創建線程,因爲在_beginthreadex函數中會爲每個線程分配一個tiddata結構體,然後會調用CreateThread函數來創建線程(這是系統創建線程的唯一方法)。

      不應該調用的兩個運行期函數:_beginthread和_endthread。

      獲取線程和進程的僞句柄,這些句柄對引用計數沒有影響,運用他們調用CloseHandle將會返回FALSE:

HANDLE GetCurrentProcess();
HANDLE GetCurrentThread();

        獲取線程和進程的獨一無二的ID:

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