首先在此感謝 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;
}