天天看點

C++ 之 _beginThreadex的用法 與 createThread 多線程的概念差別 (二)

首先在此感謝 MoreWindows 秒殺多線程面試題系列讓我成長和學習!

在此再一次真心的感謝!

在學校研究室的時候,剛剛做嵌入式的時候,導師們讓我接觸多線程的時候,都是使用CreateThread,也許很多朋友和我一樣。

最近自己看書的時候卻出現顯了一些疑問?

引申閱讀: 

關于_beginthreadex和CreateThread的差別

先來談談概念吧!例子這個東西稍後附上!

下面是關于_beginthreadex的一些要點:

•每個線程均獲得由C/C++運作期庫的堆棧配置設定的自己的tiddata記憶體結構。(tiddata結構位于Mtdll.h檔案中的VisualC++源代碼中)。

•傳遞給_beginthreadex的線程函數的位址儲存在tiddata記憶體塊中。傳遞給該函數的參數也儲存在該資料塊中。

•_beginthreadex确實從内部調用CreateThread,因為這是作業系統了解如何建立新線程的唯一方法。

•當調用CreatetThread時,它被告知通過調用_threadstartex而不是pfnStartAddr來啟動執行新線程。還有,傳遞給線程函數的參數是tiddata結構而不是pvParam的位址。

•如果一切順利,就會像CreateThread那樣傳回線程句柄。如果任何操作失敗了,便傳回NULL。

4) _endthreadex的一些要點:

•C運作期庫的_getptd函數内部調用作業系統的TlsGetValue函數,該函數負責檢索調用線程的tiddata記憶體塊的位址。

•然後該資料塊被釋放,而作業系統的ExitThread函數被調用,以便真正撤消該線程。當然,退出代碼要正确地設定和傳遞。

5)雖然也提供了簡化版的的_beginthread和_endthread,但是可控制性太差,是以一般不使用。

6)線程handle因為是核心對象,是以需要在最後closehandle。

7)更多的API:HANDLE GetCurrentProcess();HANDLE GetCurrentThread();DWORD GetCurrentProcessId();DWORD GetCurrentThreadId()。DWORD SetThreadIdealProcessor(HANDLE hThread,DWORD dwIdealProcessor);BOOL SetThreadPriority(HANDLE hThread,int nPriority);BOOL SetPriorityClass(GetCurrentProcess(),  IDLE_PRIORITY_CLASS);BOOL GetThreadContext(HANDLE hThread,PCONTEXT pContext);BOOL SwitchToThread();

三注意

1)C++主線程的終止,同時也會終止所有主線程建立的子線程,不管子線程有沒有執行完畢。是以上面的代碼中如果不調用WaitForSingleObject,則2個子線程t1和t2可能并沒有執行完畢或根本沒有執行。

2)如果某線程挂起,然後有調用WaitForSingleObject等待該線程,就會導緻死鎖。是以上面的代碼如果不調用resumethread,則會死鎖。

關于_beginthreadex和CreateThread的差別我就不做說明了,這個很 容易找到的。我們隻要知道一個問題:_beginthreadex是一個C運作時庫的函數,CreateThread是一個系統API函 數,_beginthreadex内部調用了CreateThread。隻是以所有的書都強調記憶體洩漏的問題是因為_beginthreadex函數在創 建線程的時候配置設定了一個堆結構并和線程本身關聯起來,我們把這個結構叫做tiddata結構,是通過線程本地存儲器TLS于線程本身關聯起來。我們傳入的 線程入口函數就儲存在這個結構中。tiddata的作用除了儲存線程函數入口位址之外,還有一個重要的作用就是:C運作時庫中有些函數需要通過這個結構來 儲存和擷取一些資料,比如說errno之類的線程全局變量。這點才是最重要的。

當一個線程調用一個要求tiddata結構的運作時庫函數的時候,将發生下面的情況:

運作時庫函數試圖TlsGetv alue擷取線程資料塊的位址,如果沒有擷取到,函數就會 現場配置設定一個 tiddata結構,并且和線程相關聯,于是問題出現了,如果不通過_endthreadex函數來終結線程的話,這個結構将不會被撤銷,記憶體洩漏就會出 現了。但通常情況下,我們都不推薦使用_endthreadex函數來結束線程,因為裡面包含了ExitThread調用。

找到了記憶體洩漏的具體原因,我們可以這樣說:隻要在建立的線程裡面不使用一些要求tiddata結構的運作時庫函數,我們的記憶體時安全的。是以,前面說的那句話應該這樣說才完善:

“絕對不要調用系統自帶的CreateThread函數建立新的線程,而應該使用_beginthreadex,除非你線上程中絕不使用需要tiddata結構的運作時庫函數”

這個需要tiddata結構的函數有點麻煩了,在侯捷的《win32多線程程式設計》一書中這樣說到:

”如果在除主線程之外的任何線程中進行一下操作,你就應該使用多線程版本的C runtime library,并使用_beginthreadex和_endthreadex:

1 使用malloc()和free(),或是new和delete

2 使用stdio.h或io.h裡面聲明的任何函數

3 使用浮點變量或浮點運算函數

4 調用任何一個使用了靜态緩沖區的runtime函數,比如:asctime(),strtok()或rand()

那麼概念先介紹到這裡了,下面将進行小Demo的示範,未完待續!敬請等待!

那麼我們先來一個createThrea示例

函數功能:建立線程

函數原型:

HANDLEWINAPICreateThread(

 LPSECURITY_ATTRIBUTESlpThreadAttributes,

 SIZE_TdwStackSize,

  LPTHREAD_START_ROUTINElpStartAddress,

 LPVOIDlpParameter,

 DWORDdwCreationFlags,

 LPDWORDlpThreadId

);

函數說明:

第一個參數表示線程核心對象的安全屬性,一般傳入NULL表示使用預設設定。

第二個參數表示線程棧空間大小。傳入0表示使用預設大小(1MB)。

第三個參數表示新線程所執行的線程函數位址,多個線程可以使用同一個函數位址。

第四個參數是傳給線程函數的參數。

第五個參數指定額外的标志來控制線程的建立,為0表示線程建立之後立即就可以進行排程,如果為CREATE_SUSPENDED則表示線程建立後暫停運作,這樣它就無法排程,直到調用ResumeThread()。

第六個參數将傳回線程的ID号,傳入NULL表示不需要傳回該線程ID号。

函數傳回值:

成功傳回新線程的句柄,失敗傳回NULL。

 MSDN 參考示例:

#include <windows.h> <WINDOWS.H>
#include <strsafe.h> <STRSAFE.H>
#include <stdio.h><STDIO.H>

#define BUF_SIZE 255

//------------------------------------------
// A function to Display the message
// indicating in which tread we are
//------------------------------------------
void DisplayMessage (HANDLE hScreen, 
     char *ThreadName, int Data, int Count)
{

    TCHAR msgBuf[BUF_SIZE];
    size_t cchStringSize;
    DWORD dwChars;

    // Print message using thread-safe functions.
    StringCchPrintf(msgBuf, BUF_SIZE, 
       TEXT("Executing iteration %02d of %s" 
       " having data = %02d \n"), 
       Count, ThreadName, Data); 
    StringCchLength(msgBuf, BUF_SIZE, &cchStringSize);
    WriteConsole(hScreen, msgBuf, cchStringSize, 
                 &dwChars, NULL);
    Sleep(1000);
}

//-------------------------------------------
// A function that represents Thread number 1
//-------------------------------------------
DWORD WINAPI Thread_no_1( LPVOID lpParam ) 
{

    int     Data = 0;
    int     count = 0;
    HANDLE  hStdout = NULL;
    
    // Get Handle To screen.
    // Else how will we print?
    if( (hStdout = 
         GetStdHandle(STD_OUTPUT_HANDLE)) 
         == INVALID_HANDLE_VALUE )  
    return 1;

    // Cast the parameter to the correct
    // data type passed by callee i.e main() in our case.
    Data = *((int*)lpParam); 

    for (count = 0; count <= 4; count++ )
    {
       DisplayMessage (hStdout, "Thread_no_1", Data, count);
    }
    
    return 0; 
} 

//-------------------------------------------
// A function that represents Thread number 2
//-------------------------------------------
DWORD WINAPI Thread_no_2( LPVOID lpParam ) 
{

    int     Data = 0;
    int     count = 0;
    HANDLE  hStdout = NULL;
    
    // Get Handle To screen. Else how will we print?
    if( (hStdout = 
         GetStdHandle(STD_OUTPUT_HANDLE)) == 
         INVALID_HANDLE_VALUE )  
    return 1;

    // Cast the parameter to the correct
    // data type passed by callee i.e main() in our case.
    Data = *((int*)lpParam); 

    for (count = 0; count <= 7; count++ )
    {
      DisplayMessage (hStdout, "Thread_no_2", Data, count);
    }
    
    return 0; 
} 

//-------------------------------------------
// A function that represents Thread number 3
//-------------------------------------------
DWORD WINAPI Thread_no_3( LPVOID lpParam ) 
{
    int     Data = 0;
    int     count = 0;
    HANDLE  hStdout = NULL;

    // Get Handle To screen. Else how will we print?
    if( (hStdout = 
         GetStdHandle(STD_OUTPUT_HANDLE)) 
         == INVALID_HANDLE_VALUE )  
    return 1;

    // Cast the parameter to the correct
    // data type passed by callee i.e main() in our case.
    Data = *((int*)lpParam); 

    for (count = 0; count <= 10; count++ )
    {
     DisplayMessage (hStdout, "Thread_no_3", Data, count);
    }
    
    return 0; 
} 

 
void main()
{
    // Data of Thread 1
    int Data_Of_Thread_1 = 1;
    // Data of Thread 2
    int Data_Of_Thread_2 = 2;
    // Data of Thread 3
    int Data_Of_Thread_3 = 3;
    // variable to hold handle of Thread 1
    HANDLE Handle_Of_Thread_1 = 0;
    // variable to hold handle of Thread 1 
    HANDLE Handle_Of_Thread_2 = 0;
    // variable to hold handle of Thread 1
    HANDLE Handle_Of_Thread_3 = 0;
    // Aray to store thread handles 
    HANDLE Array_Of_Thread_Handles[3];

    // Create thread 1.
    Handle_Of_Thread_1 = CreateThread( NULL, 0, 
           Thread_no_1, &Data_Of_Thread_1, 0, NULL);  
    if ( Handle_Of_Thread_1 == NULL)
        ExitProcess(Data_Of_Thread_1);
    
    // Create thread 2.
    Handle_Of_Thread_2 = CreateThread( NULL, 0, 
           Thread_no_2, &Data_Of_Thread_2, 0, NULL);  
    if ( Handle_Of_Thread_2 == NULL)
        ExitProcess(Data_Of_Thread_2);
    
    // Create thread 3.
    Handle_Of_Thread_3 = CreateThread( NULL, 0, 
           Thread_no_3, &Data_Of_Thread_3, 0, NULL);  
    if ( Handle_Of_Thread_3 == NULL)
        ExitProcess(Data_Of_Thread_3);


    // Store Thread handles in Array of Thread
    // Handles as per the requirement
    // of WaitForMultipleObjects() 
    Array_Of_Thread_Handles[0] = Handle_Of_Thread_1;
    Array_Of_Thread_Handles[1] = Handle_Of_Thread_2;
    Array_Of_Thread_Handles[2] = Handle_Of_Thread_3;
    
    // Wait until all threads have terminated.
    WaitForMultipleObjects( 3, 
        Array_Of_Thread_Handles, TRUE, INFINITE);

    printf("Since All threads executed" 
           " lets close their handles \n");

    // Close all thread handles upon completion.
    CloseHandle(Handle_Of_Thread_1);
    CloseHandle(Handle_Of_Thread_2);
    CloseHandle(Handle_Of_Thread_3);
}
 
 
beginthreadex Demo
 
class MyClass
{
    _ beginthreadex(NULL,0, function, 0,0,0);      
}
 
int main ()
{
   MyClass RunOldMain;
   return;
}
void  function(LPWSTR lpParam )
{
 // function is plain 'C' 
 return;
}
           

繼續閱讀