AfxBeginThread、BeginThread和BeginThreadex實際上是編譯器對CreateThread的封裝。
一、CreateThread:
Windows的API函數(SDK函數的標准形式,直截了當的創建方式,任何場合都可以使用),提供操作系統級別的創建線程的操作,且僅限於工作者線程。
//@線程創建成功返回新線程的句柄,失敗返回NULL HANDLE WINAPI CreateThread( //表示線程內核對象的安全屬性,一般傳入NULL表示使用默認設置 _In_opt_ LPSECURITY_ATTRIBUTES lpThreadAttributes, //表示線程棧空間大小。傳入0表示使用默認大小(1MB) _In_ SIZE_T dwStackSize, //表示新線程所執行的線程函數地址,多個線程可以使用同一個函數地址。 _In_ LPTHREAD_START_ROUTINE lpStartAddress, //是傳給線程函數的參數 _In_opt_ LPVOID lpParameter, //指定額外的標志來控制線程的創建,為0表示線程創建之后立即就可以進行調度,如果為CREATE_SUSPENDED則表示線程創建后暫停運行,這樣它就無法調度,直到調用ResumeThread()。 _In_ DWORD dwCreationFlags, //將返回線程的ID號,傳入NULL表示不需要返回該線程ID號。 _Out_opt_ LPDWORD lpThreadId );
/* 創建第一個線程。主進程結束,則撤銷線程。 */ #include<Windows.h> #include<stdio.h> DWORD WINAPI ThreadFunc(LPVOID); void main() { HANDLE hThread; DWORD threadId; hThread = CreateThread(NULL, 0, ThreadFunc, 0, 0, &threadId); // 創建線程 printf("我是主線程, pid = %d\n", GetCurrentThreadId()); //輸出主線程pid Sleep(2000); } DWORD WINAPI ThreadFunc(LPVOID p) { printf("我是子線程, pid = %d\n", GetCurrentThreadId()); //輸出子線程pid return 0; }
在實際使用中盡量使用_beginthreadex()來創建線程,在博客中使用 CreateThread()l來創建線程其實是一種不太好的方法,不過這里只做原理分析,不用在實際項目中。
二、WaitForSingleObject / WaitForMultipleObjects
需要等待某一線程完成了特定的操作后再繼續做其他事情,要實現這個目的,可以使用Windows API函數WaitForSingleObject,或者WaitForMultipleObjects。這兩個函數都會等待Object被標為有信號(signaled)時才返回。只要是Windows創建的Object都會被賦予一個狀態量。如果Object被激活了,或者正在使用,那么該Object就是無信號,也就是不可用;另一方面,如果Object可用了,那么它就恢復有信號了。
/*返回值: WAIT_ABANDONED 0x00000080:當hHandle為mutex時,如果擁有mutex的線程在結束時沒有釋放核心對象會引發此返回值。 WAIT_OBJECT_0 0x00000000 :指定的對象出有有信號狀態 WAIT_TIMEOUT 0x00000102:等待超時 WAIT_FAILED 0xFFFFFFFF :出現錯誤,可通過GetLastError得到錯誤代碼 */ DWORD WINAPI WaitForSingleObject( /*參數1: 對象的句柄,可以是以下幾種: Change notification Console input Event Memory resource notification Mutex Process Semaphore Thread Waitable timer */ _In_ HANDLE hHandle, /*參數2: 等待時間,以毫秒為單位。參數dwMilliseconds有兩個具有特殊意義的值:0和INFINITE。若為0,則該函數立即返回;若為INFINITE,則線程一直被掛起,直到hHandle所指向的對象變為有信號狀態時為止。 */ _In_ DWORD dwMilliseconds );
//@返回值類型同上 DWORD WINAPI WaitForMultipleObjects( //為等待的內核對象個數,可以是0到MAXIMUM_WAIT_OBJECTS(64)中的一個值 _In_ DWORD nCount, //為一個存放被等待的內核對象句柄的數組 _In_ const HANDLE *lpHandles, //是否等到所有內核對象為已通知狀態后才返回,如果為TRUE,則只有當等待的所有內核對象為已通知狀態時函數才返回,如果為FALSE,則只要一個內核對象為已通知狀態,則該函數返回。 _In_ BOOL bWaitAll, //為等待時間,和WaitForSingleObject中的dwMilliseconds參數類似 _In_ DWORD dwMilliseconds );
三、beginthread / beginthreadex:
MS對C-Runtime庫的擴展SDK函數,首先針對C Runtime庫做了一些初始化的工作,以保證C Runtime庫工作正常,然后,調用CreateThread真正創建線程。beginthread是_beginthreadex的功能子集,雖然_beginthread內部是調用_beginthreadex但他屏蔽了象安全特性這樣的功能,例如,如果使用_beginthread,就無法創建帶有安全屬性的新線程,無法創建暫停的線程,也無法獲得線程的ID值。_beginthread與CreateThread不是同等級別,_beginthreadex和CreateThread在功能上完全可替代 。
注意:盡量不要調用CreateThread。相反,應該使用Visual C++運行期庫函數_beginthreadex,原因如下: 考慮標准C運行時庫的一些變量和函數,如errno,這是一個全局變量。必須存在一種機制,使得每個線程能夠引用它自己的errno變量,又不觸及另一線程的errno變量._beginthreadex就為每個線程分配自己的tiddata內存結構。該結構保存了許多像errno這樣的變量和函數的值、地址,通過線程局部存儲將tiddata與線程聯系起來。具體實現在Threadex.c中有,結束線程使用函數_endthreadex函數,釋放掉線程的tiddata數據塊。對於線程的支持是后來的事! 這也導致了許多CRT的函數在多線程的情況下必須有特殊的支持,不能簡單的使用CreateThread。
CRT的函數庫在線程出現之前就已經存在,所以原有的CRT不能真正支持線程,這導致我們在編程的時候有了CRT庫的選擇,在MSDN中查閱CRT的函數時都有:
Libraries
LIBC.LIB Single thread static library, retail version
LIBCMT.LIB Multithread static library, retail version
MSVCRT.LIB Import library for MSVCRT.DLL, retail version
大多的CRT函數都可以在CreateThread線程中使用,看資料說只有signal()函數不可以,會導致進程終止!但可以用並不是說沒有問題!
有些CRT的函數象malloc(), fopen(), _open(), strtok(), ctime(), 或localtime()等函數需要專門的線程局部存儲的數據塊,這個數據塊通常需要在創建線程的時候就建立,如果使用CreateThread,這個數據塊就沒有建立,然后會怎樣呢?在這樣的線程中還是可以使用這些函數而且沒有出錯,實際上函數發現這個數據塊的指針為空時,會自己建立一個,然后將其與線程聯系在一起,這意味着如果你用CreateThread來創建線程,然后使用這樣的函數,會有一塊內存在不知不覺中創建,遺憾的是,這些函數並不將其刪除,而CreateThread和ExitThread也無法知道這件事,於是就會有Memory leak,在線程頻繁啟動的軟件中(比如某些服務器軟件),遲早會讓系統的內存資源耗盡!
_beginthreadex和_endthreadex就對這個內存塊做了處理,所以沒有問題!(不會有人故意用CreateThread創建然后用_endthreadex終止吧,而且線程的終止最好不要顯式的調用終止函數,自然退出最好!) 如果在除主線程之外的任何線程中進行一下操作,你就應該使用多線程版本的C runtime library,並使用_beginthreadex和_endthreadex:
1 使用malloc()和free(),或是new和delete
2 使用stdio.h或io.h里面聲明的任何函數
3 使用浮點變量或浮點運算函數
4 調用任何一個使用了靜態緩沖區的runtime函數,比如:asctime(),strtok()或rand()
Handle的問題,_beginthread的對應函數_endthread自動的調用了CloseHandle,而_beginthreadex的對應函數_endthreadex則沒有,所以CloseHandle無論如何都是要調用的不過_endthread可以幫你執行自己不必寫,其他兩種就需要自己寫!(Jeffrey Richter強烈推薦盡量不用顯式的終止函數,用自然退出的方式,自然退出當然就一定要自己寫CloseHandle)
//頭文件:process.h //函數原型: unsigned long _beginthreadex( //安全屬性,NULL為默認安全屬性 void *security, //指定線程堆棧的大小。如果為0,則線程堆棧大小和創建它的線程的相同。一般用0 unsigned stack_size, //指定線程函數的地址,也就是線程調用執行的函數地址(用函數名稱即可,函數名稱就表示地址,注意的是函數訪問方式一定是__stdcall,函數返回值一定是unsigned,函數參數一定是void*) unsigned ( __stdcall *start_address )( void * ), //傳遞給線程的參數的指針,可以通過傳入對象的指針,在線程函數中再轉化為對應類的指針 void *arglist, //線程初始狀態,0:立即運行;CREATE_SUSPEND:懸掛(如果出事狀態定義為懸掛,就要調用ResumeThread(HANDLE) 來激活線程的運行) unsigned initflag, //用於記錄線程ID的地址 unsigned *thrdaddr );
四、AfxBeginThread:
MFC中線程創建的MFC函數,它簡化了操作或讓線程能夠響應消息,即可用於界面線程,也可以用於工作者線程,但要注意盡量不要在一個MFC程序中使用_beginthreadex()或CreateThread()。
//用戶界面線程的AfxBeginThread的原型如下: CWinThread* AFXAPI AfxBeginThread( //從CWinThread派生的RUNTIME_CLASS類 CRuntimeClass* pThreadClass, //指定線程優先級,如果為0,則與創建該線程的線程相同; int nPriority, //指定線程的堆棧大小,如果為0,則與創建該線程的線程相同; UINT nStackSize, //一個創建標識,如果是CREATE_SUSPENDED,則在懸掛狀態創建線程,在線程創建后線程掛起,否則線程在創建后開始線程的執行。 DWORD dwCreateFlags, //線程的安全屬性,NT下有用 LPSECURITY_ATTRIBUTES lpSecurityAttrs)
//工作者線程的AfxBeginThread的原型如下: //@功時返回一個指向新線程的線程對象的指針,否則NULL CWinThread* AfxBeginThread( // 線程的入口函數,聲明一定要如下: UINT MyThreadFunction(LPVOID pParam),不能設置為NULL AFX_THREADPROC pfnThreadProc, //傳遞入線程的參數,注意它的類型為:LPVOID,所以我們可以傳遞一個結構體入線程. LPVOID lParam, // 線程的優先級,一般設置為 0 .讓它和主線程具有共同的優先級. int nPriority = THREAD_PRIORITY_NORMAL, //指定新創建的線程的棧的大小.如果為 0,新創建的線程具有和主線程一樣的大小的棧 UINT nStackSize = 0, // 指定創建線程以后,線程有怎么樣的標志.可以指定兩個值: CREATE_SUSPENDED : 線程創建以后,會處於掛起狀態,直到調用:ResumeThread 0 : 創建線程后就開始運行. DWORD dwCreateFlags = 0, // 指向一個 SECURITY_ATTRIBUTES 的結構體,用它來標志新創建線程的安全性.如果為 NULL, 那么新創建的線程就具有和主線程一樣的安全性. 如果要在線程內結束線程,可以在線程內調用 AfxEndThread. LPSECURITY_ATTRIBUTES lpSecurityAttrs = NULL );//用於創建工作者線程