电竞比分网-中国电竞赛事及体育赛事平台

分享

Windows線程創(chuàng)建、退出及資源釋放

 leon0821 2014-02-18
可以通過以下幾種方法創(chuàng)建一個線程
1、CreateThread
2、_beginthread
3、_beginthreadex
4、AfxBeginThread
--------------------------------------------------------------------------------------

1、CreateThread

函數(shù)原型

HANDLE CreateThread( LPSECURITY_ATTRIBUTES lpThreadAttributes, // SD
   SIZE_T dwStackSize, // initial stack size
   LPTHREAD_START_ROUTINE lpStartAddress, // thread function
   LPVOID lpParameter, // thread argument
   DWORD dwCreationFlags, // creation option
   LPDWORD lpThreadId // thread identifier);

參數(shù)
lpThreadAttributes:指向SECURITY_ATTRIBUTES型態(tài)的結(jié)構(gòu)的指針。在Windows 98中忽略該參數(shù)。在Windows NT中,NULL使用默認安全性,不可以被子線程繼承,否則需要定義一個結(jié)構(gòu)體將它的bInheritHandle成員初始化為TRUE
dwStackSize:設置初始棧的大小,以字節(jié)為單位,如果為0,那么默認將使用與調(diào)用該函數(shù)的線程相同的??臻g大小。任何情況下,Windows根據(jù)需要動態(tài)延長堆棧的大小。
lpStartAddress:指向線程函數(shù)的指針,必須以下列形式聲明:

DWORD WINAPI ThreadProc (LPVOID lpParam) ,格式不正確將無法調(diào)用成功。
//也可以直接調(diào)用void類型
//但lpStartAddress要這樣通過LPTHREAD_START_ROUTINE轉(zhuǎn)換如:(LPTHREAD_START_ROUTINE)MyVoid
//然后在線程聲明為:
void MyVoid()
{
return;
}
lpParameter:向線程函數(shù)傳遞的參數(shù),是一個指向結(jié)構(gòu)的指針,不需傳遞參數(shù)時,為NULL。
dwCreationFlags :線程標志,可取值如下:
--------------------------------------------------------------------------------------
CREATE_SUSPENDED(0x00000004):創(chuàng)建一個掛起的線程,
0:表示創(chuàng)建后立即激活。
STACK_SIZE_PARAM_IS_A_RESERVATION(0x00010000):dwStackSize參數(shù)指定初始的保留堆棧的大小,否則,dwStackSize指定提交的大小。該標記值在Windows 2000/NT and Windows Me/98/95上不支持。
--------------------------------------------------------------------------------------
lpThreadId:保存新線程的id。

返回值
函數(shù)成功,返回線程句柄;函數(shù)失敗返回false。若不想返回線程ID,設置值為NULL。

實例:

PMYDATA pData; pData = (PMYDATA)HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, sizeof(MYDATA)); if( pData == NULL ) ExitProcess(2); // Generate unique data for each thread. pData->val1 = i; pData->val2 = i+100; HANDLE hThread = CreateThread( NULL, // default security attributes 0, // use default stack size ThreadProc, // thread function pData, // argument to thread function 0, // use default creation flags &dwThreadId[i]); // returns the thread identifier // Check the return value for success. if (hThread == NULL) { ExitProcess(i); }


2、_beginthread

函數(shù)原型

uintptr_t _beginthread( void( *start_address )( void * ), unsigned stack_size, void *arglist );

參數(shù)
start_address:新線程的起始地址 ,指向新線程調(diào)用的函數(shù)的起始地址
stack_size新線程的堆棧大小,可以為0
arglist 傳遞給線程的參數(shù)列表,無參數(shù)是為NULL 

返回值
假如成功,函數(shù)將返回一個處理信息對這個新創(chuàng)建的線程。如果失敗_beginthread將返回-1。

引用頭文件
#include <process.h>

實例

m_hThreadHandle = HANDLE(_beginthread(CAppLog::LogProcThread, 0, this));


3、_beginthreadex

函數(shù)原型(MSDN)

unsigned long _beginthreadex( void *security, 

unsigned stack_size, 

unsigned ( __stdcall *start_address )( void * ), 

void *arglist, 

unsigned initflag, 

unsigned *thrdaddr );

參數(shù)
security:安全屬性,NULL為默認安全屬性
stack_size:指定線程堆棧的大小。如果為0,則線程堆棧大小和創(chuàng)建它的線程的相同。一般用0
start_address :指定線程函數(shù)的地址,也就是線程調(diào)用執(zhí)行的函數(shù)地址(用函數(shù)名稱即可,函數(shù)名稱就表示地址)
arglist:傳遞給線程的參數(shù)的指針,可以通過傳入對象的指針,在線程函數(shù)中再轉(zhuǎn)化為對應類的指針
initflag:線程初始狀態(tài),0:立即運行;CREATE_SUSPEND:suspended(懸掛)
thrdaddr :用于記錄線程ID的地址

返回值
與_beginthread()不同的是:_beginthread返回-1表示失敗, 而_beginthreadex()返回0表示失??!

引用頭文件
#include <process.h>

實例:

HANDLE hThread; hThread = (HANDLE)_beginthreadex( NULL, 0, &SIPVoIPLink::ReceivingThrd, (LPVOID)this, 0, &threadID ); if(hThread == NULL) return false;



4、AfxBeginThread

這個看起來就直接了,MFC封裝的函數(shù)。
在MFC中,用戶界面線程工作者線程都是由AfxBeginThread創(chuàng)建的,MFC提供了兩個重載版的AfxBeginThread,一個用于用戶界面線程,另一個用于工作者線程,分別有如下的原型和過程:

原型一(用戶界面線程

CWinThread* AFXAPI AfxBeginThread( CRuntimeClass* pThreadClass,    int nPriority,    UINT nStackSize,    DWORD dwCreateFlags,    LPSECURITY_ATTRIBUTES lpSecurityAttrs)

參數(shù)
pThreadClass從CWinThread派生的RUNTIME_CLASS類;
nPriority指定線程優(yōu)先級,如果為0,則與創(chuàng)建該線程的線程相同;
nStackSize指定線程的堆棧大小,如果為0,則與創(chuàng)建該線程的線程相同;
dwCreateFlags一個創(chuàng)建標識,如果是CREATE_SUSPENDED,則在懸掛狀態(tài)創(chuàng)建線程,在線程創(chuàng)建后線程掛起,否則線程在創(chuàng)建后開始線程的執(zhí)行。
lpSecurityAttrs表示線程的安全屬性,NT下有用。

原型二(工作線程)

CWinThread* AfxBeginThread( AFX_THREADPROC pfnThreadProc,    LPVOID lParam,    int nPriority = THREAD_PRIORITY_NORMAL,    UINT nStackSize = 0,    DWORD dwCreateFlags = 0,    LPSECURITY_ATTRIBUTES lpSecurityAttrs = NULL   );//用于創(chuàng)建工作者線程

參數(shù):
pfnThreadProc : 線程的入口函數(shù),聲明一定要如下: UINT MyThreadFunction(LPVOID pParam),不能設置為NULL;
pParam : 傳遞入線程的參數(shù),注意它的類型為:LPVOID,所以我們可以傳遞一個結(jié)構(gòu)體入線程.
nPriority : 線程的優(yōu)先級,一般設置為 0 .讓它和主線程具有共同的優(yōu)先級.
nStackSize : 指定新創(chuàng)建的線程的棧的大小.如果為 0,新創(chuàng)建的線程具有和主線程一樣的大小的棧
dwCreateFlags : 指定創(chuàng)建線程以后,線程有怎么樣的標志.可以指定兩個值:
--------------------------------------------------------------------------------------
CREATE_SUSPENDED : 線程創(chuàng)建以后,會處于掛起狀態(tài),直到調(diào)用:ResumeThread
0 : 創(chuàng)建線程后就開始運行.
--------------------------------------------------------------------------------------
lpSecurityAttrs : 指向一個 SECURITY_ATTRIBUTES 的結(jié)構(gòu)體,用它來標志新創(chuàng)建線程的安全性.如果為 NULL,

返回值
一個指向新線程的線程對象的指針

以上幾種創(chuàng)建線程的區(qū)別

其實windows只提供了一個創(chuàng)建線程的方法,就是CreateThread,后面三個函數(shù)都是由CreateThread間接得到。
1、_beginthread和_beginthreadex的區(qū)別
首先我們看看這兩個函數(shù)都干了什么:

uintptr_r __cdecl _beginthreadex(...) { //為即將創(chuàng)建的線程分配一個數(shù)據(jù)結(jié)構(gòu)_ptiddata ptd(per-thread data) //初始化這個數(shù)據(jù)結(jié)構(gòu),其中ptd->_thandle = (uintptr_t)(-1) //如果初始化失敗,返回(uintptr_t)(0) [_beginthread返回-1] //用傳進來的參數(shù),調(diào)用CreateThread //如果創(chuàng)建成功返回CreateThread返回的代碼 //如果創(chuàng)建失敗則釋放ptd,并返回(uintptr_t)(0) [_beginthread返回-1,而CreateThread失敗返回0,非-1] }

然后再看看這兩個函數(shù)有什么不同
(1)參數(shù)列表不同, ex版本的參數(shù)和CreateThread差不多:
(2)二者在初始化ptd失敗時返回的值不同
(3)_beginthread的參數(shù)缺少安全描述符. 而且它是創(chuàng)建線程的時候先以掛起狀態(tài)創(chuàng)建 (CreateThread會填充ptd->_thandle和ptd->_tid) 然后再ResumeThread。_beginthread是根據(jù)傳進來的參數(shù)創(chuàng)建線程
(4)失敗返回值不同,ex版本的與Windows API CreateThread返回值是一直的,這也是提倡使用后者的原因之一

2、CreateThread()、_beginthreadex()及、AfxBeginThread()
CreateThread時Windows API接口函數(shù),_beginthreadex函數(shù)是C/C++運行庫提供的函數(shù),從 _beginthreadex函數(shù)的源代碼,可以看出它的主要動作是:增加了一個名為ptd的_ptiddata的結(jié)構(gòu)的處理,然后在調(diào)用CreateThread函數(shù)。_ptiddata是每個線程都擁有自己的專用的數(shù)據(jù)結(jié)構(gòu)。AfxBeginThread是MFC封裝的啟動線程的函數(shù),里面包含了很多和MFC相關的啟動信息,而且封裝了一些常用的操作,使用起來也比較簡便。而用另外兩個函數(shù)就需要程序員對類型,安全性檢查進行更多的思考!

----------------------------------------------------------------------------------------
線程退出方式
1.線程函數(shù)返回
2.線程通過調(diào)用ExitThread終止自己(自己調(diào)用)
3.TerminateThread(本進程或其它進程的線程調(diào)用)
4.包含線程的進程終止

第一種是最完美的退出方式,應盡可能使用第一種方法以避免資源泄漏!
----------------------------------------------------------------------------------------
線程資源釋放
必須明白,一個線程終止和線程資源釋放那個不是一個概念。當一個線程以上面幾種退出方式終止之后,線程對象及資源還存在于內(nèi)存中,所以在線程終止之后還需要某種方式釋放資源。

線程的釋放方式與創(chuàng)建方式相關聯(lián):
CreateThread創(chuàng)建的線程應使用CloseHandle關閉。
_beginthread創(chuàng)建的線程應使用_endthread關閉。
_beginthreadex創(chuàng)建的線程應使用_endthreadex關閉。
AfxBeginThread創(chuàng)建的線程,如果是線程自己結(jié)束自己的線程(從CWinThread繼承出來的)就用AfxEndThread。如果外部調(diào)用的話可以用PostThreadMessage(m_nThreadID, WM_QUIT,0,0);給這個線程發(fā)送消息,線程就會結(jié)束的,其中的m_nThreadID是線程ID。

線程資源的釋放方法(常用)
1、如果對創(chuàng)建線程的引用不感興趣,可在創(chuàng)建之后直接關閉句柄

void main()

{

……

HANDLE hThread = CreateThread(NULL,0,ThreadFunc,NULL,0,NULL);

CloseHandle(hThread);

……

}

有些新手可能會有個疑問,為什么在剛創(chuàng)建完線程之后就把句柄關掉了?
要特別注意句柄和實例區(qū)別,句柄只是對實例對象的引用,用以操作對象。我們使用CreateThread創(chuàng)建了一個線程實例(可將線程整體看作一個對象),返回的線程句柄只是提供了一個方法(類似于指針)讓我們可以訪問或操作線程對象。我們這里只需要讓創(chuàng)建的線程執(zhí)行必要的代碼段就好了,之后并不需要在對其進行任何操作,所以可以直接將其CloseHandle掉,關閉句柄只是切斷了訪問線程的方式罷了,線程還是存在并運行著的。
然而,只是這樣解釋還是有所偏差,因為在很多使用CloseHandle關閉其他對象句柄的操作中都會釋放對象占用的資源,而對于線程,在調(diào)用CloseHandle之后并不會終止線程,也就不會立馬釋放線程資源(實現(xiàn)中線程還必須繼續(xù)運行),調(diào)用CloseHandle之后系統(tǒng)會遞減線程內(nèi)核對象的使用計數(shù),當線程執(zhí)行完畢(線程函數(shù)執(zhí)行完)之后也會遞減此線程內(nèi)核對象使用計數(shù),當計數(shù)為0時才會釋放線程資源!
反過來看,如果不使用CloseHandle關閉線程句柄,那么系統(tǒng)就會一直保持著對此線程內(nèi)核對象的引用,這樣,即使線程執(zhí)行完畢,使用計數(shù)也不會為0,所以線程資源不會被釋放。
因此,CloseHandle關閉線程句柄是釋放線程資源的必要途徑,但不會影響線程的正常運行!所以CloseHandle的調(diào)用位置非常靈活,即可在線程剛創(chuàng)建出來之后緊接著調(diào)用,也可以在線程執(zhí)行完之后才調(diào)用!

2、外部線程WaitForSingleObject,發(fā)現(xiàn)線程中止運行后,釋放線程相關的資源

void main()
{
  CThread thread; //一個自己定義的線程封裝類,其中有一些成員變量,Start啟動線程函數(shù),Release釋放資源函數(shù)。
  thread.Start();
  while(WaitForSingleObject(thread.m_hThread,1000) != WAIT_OBJECT_0)
{
  //TerminateThread(thread.m_hThread);//在這里如果你不想再等待,可以中止它
  Sleep(1000);
  }
  thread.Release();
}
這種方式的優(yōu)點是對線程的全面的管理。
缺陷在于,如果要即時釋放資源,必須有一個專門的外部的線程來不斷的監(jiān)視或管理線程運行狀態(tài)。

3、線程退出時將自身資源釋放

DWORD CALLBACK CThread::Thread(VOID *pParam) //線程的入口函數(shù),static函數(shù)
{
  CThread *pthread = pParam;
  ... //工作狀態(tài)設置
  pthread->Execute()
  ... //工作狀態(tài)設置
  pthread->Release() //在所有的工作做完之后進行釋放資源
  return 0;
}
這種方式的優(yōu)點是對線程的資源的時機是最準確的,缺陷是必須要保證線程在需要退出時不會阻塞。

3、在實際的工作中,我們需要這多種方式的集合來實現(xiàn)我們的功能

如有個網(wǎng)絡服務器,它給每個用戶的新建一個線程來響應它的請求,如果用戶退出,要保證相關資源要完整的釋放,并且服務器本身有停止功能,停止時,不論有多少和用戶交互,都必須中止所有線程,且釋放所有資源。
要解決的子問題:
Q1:用戶退出時資源釋放:
解決方式:
(1)使用線程自身退出時釋放的方式。
(2)使用一個外部監(jiān)視線程,發(fā)現(xiàn)線程退出時釋放相應的資源。

Q2:服務器停止時退出且釋放所有線程
設用戶線程工作狀態(tài)為停止,等待用戶線程自然退出,如果沒有退出,可能是阻塞狀態(tài)。這時使用強制退出的方法。
釋放所有用戶線程相關的資源,可以根據(jù)資源的使用時間和方式(如某些Windows資源句柄),在線程退出之前的某時機進行釋放。

下面是我實作的滿足以上要求的自身退出方式釋放資源的線程類的代碼片段:

int CThread::Stop()
{
  m_RunningMutex.Lock(); //判斷線程的工作狀態(tài),如果已經(jīng)退出,直接返回
  if(!m_bRunning)
  {
  m_RunningMutex.Unlock();
  return ERR_VTHREAD_ALREAD_STOP;
  }
  m_RunningMutex.Unlock();

  m_StopMutex.Lock();
  m_bStop = True;  //設置線程的工作狀態(tài)為停止
  m_StopMutex.Unlock();
#ifdef _WIN32
  DWORD ThreadId = GetCurrentThreadId(); //判斷是否線程自身退出,如果是,則返回,不能調(diào)用強制退出的方法
#else
  pthread_t ThreadId = pthread_self();
#endif
 if(ThreadId != m_ThreadID)
  {
  m_dwExitMode = EXIT_BY_OTHER;
  }
  else
  {
  m_dwExitMode = EXIT_BY_SELF;
  }

 if(m_dwExitMode == EXIT_BY_OTHER) //如果是服務器停止信號
 {
  int nTryTime = 0;
  bool bStopTry = false;
  while( m_bRunning && !bStopTry) //嘗試判斷線程是否停止,最好使線程自然退出
  {
    if (nTryTime > m_nStopWaitTime) bStopTry = true;
    Sleep(10000);
    nTryTime ++;
  }
  if(bStopTry) //如果線程沒有退出,就強制退出
#ifdef _WIN32
    TerminateThread(m_hThread, DEF_EXIT_CODE);
#else
    pthread_cancel(m_ThreadID);
#endif
  m_RunningMutex.Lock();
  m_bRunning = False;
  m_RunningMutex.Unlock();

  Release(); //線程已經(jīng)停止,釋放所有私有次源,這行代碼可以在服務器端,讓此函數(shù)只執(zhí)行停止功能
  }
  return 0;
}
//這是線程的主函數(shù),它包含的釋放自身私有資源的功能。
#ifdef WIN32
DWORD __stdcall CThread::ThreadProc( VOID *lpParameter )
#else
VOID* CThread::ThreadProc( VOID *lpParameter )
#endif
{
 BOOL bStop;
#ifndef _WIN32
  //pthread_detach(pthread_self());
#endif
  CThread* pThread = (CThread*)lpParameter;

  pThread->m_RunningMutex.Lock();
  pThread->m_bRunning = True;
  pThread->m_RunningMutex.Unlock();

  pThread->m_StopMutex.Lock();
  bStop = pThread->m_bStop;
  pThread->m_StopMutex.Unlock();
  if( !bStop)
  {
  pThread->Execute();
  }
  pThread->m_RunningMutex.Lock();
  pThread->m_bRunning = False;
  pThread->m_RunningMutex.Unlock();
  if(pThread->m_dwExitMode == EXIT_BY_SELF) //在這里判斷線程是否自身退出,如是:釋放私有資源
  {
  pThread->Release();
 }
 //退出線程,使線程以最自然的方式退出.
  return 0;
}

深入討論幾種創(chuàng)建線程的方式

(1)使用_beginthreadex創(chuàng)建的線程就不該用CloseHandle釋放,因為,當用_beginThread來創(chuàng)建,而用CloseHandle來關閉線程時,這時復制的全局結(jié)構(gòu)就不會被釋放了,這就有了內(nèi)存的泄漏。這就是很多資料所說的內(nèi)存泄漏問題的真正的原因。

(2)不要在一個MFC程序中使用_beginthreadex()或CreateThread()。這句話的意思是由于AfxBeginThread()是MFC封裝的啟動線程的函數(shù),里面包含了很多和MFC相關的啟動信息,而且封裝了一些常用的操作,使用起來也比較簡便。而用另外兩個函數(shù)就需要程序員對類型,安全性檢查進行更多的思考!

(3)用_beginthreadex()函數(shù)應該是最佳選擇,因為_beginthreadex()函數(shù)是CRun-timeLibrary中的函數(shù),函數(shù)的參數(shù)和數(shù)據(jù)類型都是CRun-timeLibrary中的類型,這樣在啟動線程時就不需要進行Windows數(shù)據(jù)類型和CRun-timeLibrary中的數(shù)據(jù)類型之間的轉(zhuǎn)化。減低了線程啟動時的資源消耗和時間的消耗!

(4)在C程序中,幾乎都要用到new和delete,難道只有使用_beginthreadex()?不,因為MFC也是C類庫(只不過是Microsoft的C類庫,不是標準的C類庫),在MFC中也封裝了new和delete兩中運算符,所以用到new和delete的地方不一定非要使用_beginthreadex()函數(shù),用其他兩個函數(shù)都可以!其實在程序中使用上面的哪個函數(shù)并不是絕對的,書的作者只不過是提了一個更佳的搭配方法,我在MFC程序中也經(jīng)常使用_beginthreadex()和CreateThread()這兩個函數(shù),運行的效果也沒有多大的區(qū)別,有的時候只是需要你額外的進行一些類型檢查和其他的一些轉(zhuǎn)化操作,其余沒有其他不妥! 創(chuàng)建線程只有一個方法是::CreateThread()。_beginthreadex()、AfxBeginThread()等內(nèi)部都是調(diào)用這個函數(shù)的,因為操作系統(tǒng)只提供這一個接口C靜態(tài)庫比WINDOWS出來還早,就別提多線程了,所以他對多線程的支持不是很好,但后悔也來不急,但也不能怪人家。

(5)C運行庫_beginthreadex()。他經(jīng)過一些處理后,再調(diào)用CreateThread()如果要強制結(jié)束的話也最好用_endthreadex結(jié)束,因為他也要一些處理。 總結(jié)上面的內(nèi)容,當然《Windows核心編程》上面得說法是比較權(quán)威的。所以,在對線程的結(jié)構(gòu)、運行還不是很了解的時候最好還是按照書上的來。這樣能夠避免一些可能出現(xiàn)的莫名奇妙的錯誤,也省去的一些其他結(jié)構(gòu)處理的考慮。當你清楚地知道線程的結(jié)構(gòu)與運行機制,以及了解各個函數(shù)對CreateThread函數(shù)的封裝的時候,大概那時候就能夠應用自如了

    本站是提供個人知識管理的網(wǎng)絡存儲空間,所有內(nèi)容均由用戶發(fā)布,不代表本站觀點。請注意甄別內(nèi)容中的聯(lián)系方式、誘導購買等信息,謹防詐騙。如發(fā)現(xiàn)有害或侵權(quán)內(nèi)容,請點擊一鍵舉報。
    轉(zhuǎn)藏 分享 獻花(0

    0條評論

    發(fā)表

    請遵守用戶 評論公約

    類似文章 更多