進程是一個可執(zhí)行的程序,由私有虛擬地址空間、代碼、數(shù)據(jù)和其他操作系統(tǒng)資源(如進程創(chuàng)建的文件、管道、同步對象等)組成。一個應用程序可以有一個或多個進程,一個進程可以有一個或多個線程,其中一個是主線程。
線程是操作系統(tǒng)分時調度分配CPU時間的基本實體。一個線程可以執(zhí)行程序的任意部分的代碼,即使這部分代碼被另一個線程并發(fā)地執(zhí)行;一個進程的所有線程共享它的虛擬地址空間、全局變量和操作系統(tǒng)資源。
之所以有線程這個概念,是因為以線程而不是進程為調度對象效率更高:
因為MFC沒有提供類處理進程,所以直接使用了Win32 API函數(shù)。
調用CreateProcess函數(shù)創(chuàng)建新的進程,運行指定的程序。CreateProcess的原型如下:
BOOL CreateProcess(
LPCTSTR lpApplicationName,
LPTSTR lpCommandLine,
LPSECURITY_ATTRIBUTES lpProcessAttributes,
LPSECURITY_ATTRIBUTES lpThreadAttributes,
BOOL bInheritHandles,
DWORD dwCreationFlags,
LPVOID lpEnvironment,
LPCTSTR lpCurrentDirectory,
LPSTARTUPINFO lpStartupInfo,
LPPROCESS_INFORMATION lpProcessInformation
);
其中:
lpApplicationName指向包含了要運行模塊名字的字符串。
lpCommandLine指向命令行字符串。
lpProcessAttributes描述進程的安全性屬性,NT下有用。
lpThreadAttributes描述進程初始線程(主線程)的安全性屬性,NT下有用。
bInHeritHandles表示子進程(被創(chuàng)建的進程)是否可以繼承父進程的句柄??梢岳^承的句柄有線程句柄、有名或無名管道、互斥對象、事件、信號量、映像文件、普通文件和通訊端口等;還有一些句柄不能被繼承,如內存句柄、DLL實例句柄、GDI句柄、URER句柄等等。
子進程繼承的句柄由父進程通過命令行方式或者進程間通訊(IPC)方式由父進程傳遞給它。
dwCreationFlags表示創(chuàng)建進程的優(yōu)先級類別和進程的類型。創(chuàng)建進程的類型分控制臺進程、調試進程等;優(yōu)先級類別用來控制進程的優(yōu)先級別,分Idle、Normal、High、Real_time四個類別。
lpEnviroment指向環(huán)境變量塊,環(huán)境變量可以被子進程繼承。
lpCurrentDirectory指向表示當前目錄的字符串,當前目錄可以繼承。
lpStartupInfo指向StartupInfo結構,控制進程的主窗口的出現(xiàn)方式。
lpProcessInformation指向PROCESS_INFORMATION結構,用來存儲返回的進程信息。
從其參數(shù)可以看出創(chuàng)建一個新的進程需要指定什么信息。
從上面的解釋可以看出,一個進程包含了很多信息。若進程創(chuàng)建成功的話,返回一個進程信息結構類型的指針。進程信息結構如下:
typedef struct _PROCESS_INFORMATION {
HANDLE hProcess;
HANDLE hThread;
DWORD dwProcessId;
DWORD dwThreadId;
}PROCESS_INFORMATION;
進程信息結構包括進程句柄,主線程句柄,進程ID,主線程ID。
進程在以下情況下終止:
使用CreateThread函數(shù)創(chuàng)建線程,CreateThread的原型如下:
HANDLE CreateThread(
LPSECURITY_ATTRIBUTES lpThreadAttributes,
DWORD dwStackSize,
LPTHREAD_START_ROUTINE lpStartAddress,
LPVOID lpParameter,
DWORD dwCreationFlags, // creation flags
LPDWORD lpThreadId
);
其中:
lpThreadAttributes表示創(chuàng)建線程的安全屬性,NT下有用。
dwStackSize指定線程棧的尺寸,如果為0則與進程主線程棧相同。
lpStartAddress指定線程開始運行的地址。
lpParameter表示傳遞給線程的32位的參數(shù)。
dwCreateFlages表示是否創(chuàng)建后掛起線程(取值CREATE_SUSPEND),掛起后調用ResumeThread繼續(xù)執(zhí)行。
lpThreadId用來存放返回的線程ID。
進程的每個優(yōu)先級類包含了五個線程的優(yōu)先級水平。在進程的優(yōu)先級類確定之后,可以改變線程的優(yōu)先級水平。用SetPriorityClass設置進程優(yōu)先級類,用SetThreadPriority設置線程優(yōu)先級水平。
Normal級的線程可以被除了Idle級以外的任意線程搶占。
以下情況終止一個線程:
當用TerminateProcess或者TerminateThread終止進程或線程時,DLL的入口函數(shù)DllMain不會被執(zhí)行(如果有DLL的話)。
如果希望每個線程都可以有線程局部(Thread local)的靜態(tài)存儲數(shù)據(jù),可以使用TLS線程局部存儲技術。TLS為進程分配一個TLS索引,進程的每個線程通過這個索引存取自己的數(shù)據(jù)變量的拷貝。
TLS對DLL是非常有用的。當一個新的進程使用DLL時,在DLL入口函數(shù)DllMain中使用TlsAlloc分配TLS索引,TLS索引就作為進程私有的全局變量被保存;以后,當該進程的新的線程使用DLL時(Attahced to DLL),DllMain給它分配動態(tài)內存并且使用TlsSetValue把線程私有的數(shù)據(jù)按索引保存。DLL函數(shù)可以使用TlsGetValue按索引讀取調用線程的私有數(shù)據(jù)。
TLS函數(shù)如下:
在進程或DLL初始化時調用,并且把返回值(索引值)作為全局變量保存。
DWORD dwTlsIndex, //TLS index to set value for
LPVOID lpTlsValue //value to be stored
);
其中:
dwTlsIndex是TlsAlloc分配的索引。
lpTlsValue是線程在TLS槽中存放的數(shù)據(jù)指針,指針指向線程要保存的數(shù)據(jù)。
線程首先分配動態(tài)內存并保存數(shù)據(jù)到此內存中,然后調用TlsSetValue保存內存指針到TLS槽。
DWORD dwTlsIndex // TLS index to retrieve value for
);
其中:
dwTlsIndex是TlsAlloc分配的索引。
當要存取保存的數(shù)據(jù)時,使用索引得到數(shù)據(jù)指針。
DWORD dwTlsIndex // TLS index to free
);
其中:
dwTlsIndex是TlsAlloc分配的索引。
當每一個線程都不再使用局部存儲數(shù)據(jù)時,線程釋放它分配的動態(tài)內存。在TLS索引不再需要時,使用TlsFree釋放索引。
同步可以保證在一個時間內只有一個線程對某個資源(如操作系統(tǒng)資源等共享資源)有控制權。共享資源包括全局變量、公共數(shù)據(jù)成員或者句柄等。同步還可以使得有關聯(lián)交互作用的代碼按一定的順序執(zhí)行。
Win32提供了一組對象用來實現(xiàn)多線程的同步。
這些對象有兩種狀態(tài):獲得信號(Signaled)或者沒有或則信號(Not signaled)。線程通過Win32 API提供的同步等待函數(shù)(Wait functions)來使用同步對象。一個同步對象在同步等待函數(shù)調用時被指定,調用同步函數(shù)地線程被阻塞(blocked),直到同步對象獲得信號。被阻塞的線程不占用CPU時間。
同步對象有:Critical_section(關鍵段),Event(事件),Mutex(互斥對象),Semaphores(信號量)。
下面,解釋怎么使用這些同步對象。
首先,定義一個關鍵段對象cs:
CRITICAL_SECTION cs;
然后,初始化該對象。初始化時把對象設置為NOT_SINGALED,表示允許線程使用資源:
InitializeCriticalSection(&cs);
如果一段程序代碼需要對某個資源進行同步保護,則這是一段關鍵段代碼。在進入該關鍵段代碼前調用EnterCriticalSection函數(shù),這樣,其他線程都不能執(zhí)行該段代碼,若它們試圖執(zhí)行就會被阻塞。
完成關鍵段的執(zhí)行之后,調用LeaveCriticalSection函數(shù),其他的線程就可以繼續(xù)執(zhí)行該段代碼。如果該函數(shù)不被調用,則其他線程將無限期的等待。
首先,調用CreateEvent函數(shù)創(chuàng)建一個事件對象,該函數(shù)返回一個事件句柄。然后,可以設置(SetEvent)或者復位(ResetEvent)一個事件對象,也可以發(fā)一個事件脈沖(PlusEvent),即設置一個事件對象,然后復位它。復位有兩種形式:自動復位和人工復位。在創(chuàng)建事件對象時指定復位形式。。
自動復位:當對象獲得信號后,就釋放下一個可用線程(優(yōu)先級別最高的線程;如果優(yōu)先級別相同,則等待隊列中的第一個線程被釋放)。
人工復位:當對象獲得信號后,就釋放所有可利用線程。
最后,使用CloseHandle銷毀創(chuàng)建的事件對象。
首先,調用CreateMutex創(chuàng)建互斥對象;然后,調用等待函數(shù),可以的話利用關鍵資源;最后,調用RealseMutex釋放互斥對象。
互斥對象可以在進程間使用,但關鍵段對象只能用于同一進程的線程之間。
在Win32中,信號量的數(shù)值變?yōu)?時給以信號。在有多個資源需要管理時可以使用信號量對象。
首先,調用CreateSemaphore創(chuàng)建一個信號量;然后,調用等待函數(shù),如果允許的話,則利用關鍵資源;最后,調用RealeaseSemaphore釋放信號量對象。
文件句柄(FILE HANDLES)
命名管道句柄(NAMED PIPE HANDELS)
控制臺輸入緩沖區(qū)句柄(CONSOLE INPUT BUFFER HANDLES)
通訊設備句柄(COMMUNICTION DEVICE HANDLES)
進程句柄(PROCESS HANDLES)
線程句柄(THREAD HANDLES)
例如,當一個進程或線程結束時,進程或線程句柄獲得信號,等待該進程或者線程結束的線程被釋放。
Win32提供了一組等待函數(shù)用來讓一個線程阻塞自己的執(zhí)行。等待函數(shù)分三類:
這類函數(shù)包括:
SignalObjectAndWait
WaitForSingleObject
WaitForSingleObjectEx
函數(shù)參數(shù)包括同步對象的句柄和等待時間等。
在以下情況下等待函數(shù)返回:
同步對象獲得信號時返回;
等待時間達到了返回:如果等待時間不限制(Infinite),則只有同步對象獲得信號才返回;如果等待時間為0,則在測試了同步對象的狀態(tài)之后馬上返回。
這類函數(shù)包括:
WaitForMultipleObjects
WaitForMultipleObjectsEx
MsgWaitForMultipleObjects
MsgWaitForMultipleObjectsEx
函數(shù)參數(shù)包括同步對象的句柄,等待時間,是等待一個還是多個同步對象等等。
在以下情況下等待函數(shù)返回:
一個或全部同步對象獲得信號時返回(在參數(shù)中指定是等待一個或多個同步對象);
等待時間達到了返回:如果等待時間不限制(Infinite),則只有同步對象獲得信號才返回;如果等待時間為0,則在測試了同步對象的狀態(tài)之后馬上返回。
這類函數(shù)包括:
MsgWaitForMultipleObjectsEx
SignalObjectAndWait
WaitForMultipleObjectsEx
WaitForSingleObjectEx
這些函數(shù)主要用于重疊(Overlapped)的I/O(異步I/O)。
在Win32 API的基礎之上,MFC提供了處理線程的類和函數(shù)。處理線程的類是CWinThread,函數(shù)是AfxBeginThread、AfxEndThread等。
表5-6解釋了CWinThread的成員變量和函數(shù)。
CWinThread是MFC線程類,它的成員變量m_hThread和m_hThreadID是對應的Win32線程句柄和線程ID。
MFC明確區(qū)分兩種線程:用戶界面線程(User interface thread)和工作者線程(Worker thread)。用戶界面線程一般用于處理用戶輸入并對用戶產(chǎn)生的事件和消息作出應答。工作者線程用于完成不要求用戶輸入的任務,如耗時計算。
Win32 API并不區(qū)分線程類型,它只需要知道線程的開始地址以便它開始執(zhí)行線程。MFC為用戶界面線程特別地提供消息泵來處理用戶界面的事件。CWinApp對象是用戶界面線程對象的一個例子,CWinApp從類CWinThread派生并處理用戶產(chǎn)生的事件和消息。
通過以下步驟創(chuàng)建一個用戶界面線程:
程序員不必從CWinThread派生新的線程類,只需要提供一個控制函數(shù),由線程啟動后執(zhí)行該函數(shù)。
然后,使用AfxBeginThread創(chuàng)建MFC線程對象和Win32線程對象。如果創(chuàng)建線程時沒有指定CREATE_SUSPENDED(創(chuàng)建后掛起),則創(chuàng)建的新線程開始執(zhí)行。
如果創(chuàng)建線程是指定了CREATE_SUSPENDED,則在適當?shù)牡胤秸{用函數(shù)ResumeThread開始執(zhí)行線程。
雖然程序員沒有從CWinThread派生類,但是MFC給工作者線程提供了缺省的CWinThread對象。
用戶界面線程和工作者線程都是由AfxBeginThread創(chuàng)建的?,F(xiàn)在,考察該函數(shù):MFC提供了兩個重載版的AfxBeginThread,一個用于用戶界面線程,另一個用于工作者線程,分別有如下的原型和過程:
用戶界面線程的AfxBeginThread的原型如下:
CWinThread* AFXAPI AfxBeginThread(
CRuntimeClass* pThreadClass,
int nPriority,
UINT nStackSize,
DWORD dwCreateFlags,
LPSECURITY_ATTRIBUTES lpSecurityAttrs)
其中:
參數(shù)1是從CWinThread派生的RUNTIME_CLASS類;
參數(shù)2指定線程優(yōu)先級,如果為0,則與創(chuàng)建該線程的線程相同;
參數(shù)3指定線程的堆棧大小,如果為0,則與創(chuàng)建該線程的線程相同;
參數(shù)4是一個創(chuàng)建標識,如果是CREATE_SUSPENDED,則在懸掛狀態(tài)創(chuàng)建線程,在線程創(chuàng)建后線程掛起,否則線程在創(chuàng)建后開始線程的執(zhí)行。
參數(shù)5表示線程的安全屬性,NT下有用。
工作者線程的AfxBeginThread的原型如下:
CWinThread* AFXAPI AfxBeginThread(
AFX_THREADPROC pfnThreadProc,
LPVOID pParam,
int nPriority,
UINT nStackSize,
DWORD dwCreateFlags,
LPSECURITY_ATTRIBUTES lpSecurityAttrs)
其中:
參數(shù)1指定控制函數(shù)的地址;
參數(shù)2指定傳遞給控制函數(shù)的參數(shù);
參數(shù)3、4、5分別指定線程的優(yōu)先級、堆棧大小、創(chuàng)建標識、安全屬性,含義同用戶界面線程。
不論哪個AfxBeginThread,首先都是創(chuàng)建MFC線程對象,然后創(chuàng)建Win32線程對象。在創(chuàng)建MFC線程對象時,用戶界面線程和工作者線程的創(chuàng)建分別調用了不同的構造函數(shù)。用戶界面線程是從CWinThread派生的,所以,要先調用派生類的缺省構造函數(shù),然后調用CWinThread的缺省構造函數(shù)。圖8-1中兩個構造函數(shù)所調用的CommonConstruct是MFC內部使用的成員函數(shù)。
MFC使用CWinThread::CreateThread創(chuàng)建線程,不論對工作者線程或用戶界面線程,都指定線程的入口函數(shù)是_AfxThreadEntry。_AfxThreadEntry調用AfxInitThread初始化線程。
CreateThread和_AfxThreadEntry在線程的創(chuàng)建過程中使用同步手段交互等待、執(zhí)行。CreateThread由創(chuàng)建線程執(zhí)行,_AfxThreadEntry由被創(chuàng)建的線程執(zhí)行,兩者通過兩個事件對象(hEvent和hEvent2)同步:
在創(chuàng)建了新線程之后,創(chuàng)建線程將在hEvent事件上無限等待直到新線程給出創(chuàng)建結果;新線程在創(chuàng)建成功或者失敗之后,觸發(fā)事件hEvent讓父線程運行,并且在hEven2上無限等待直到父線程退出CreateThread函數(shù);父線程(創(chuàng)建線程)因為hEvent的置位結束等待,繼續(xù)執(zhí)行,退出CreateThread之前觸發(fā)hEvent2事件;新線程(子線程)因為hEvent2的置位結束等待,開始執(zhí)行控制函數(shù)(工作者線程)或者進入消息循環(huán)(用戶界面線程)。
MFC在線程創(chuàng)建中使用了如下數(shù)據(jù)結構:
struct _AFX_THREAD_STARTUP
{
//傳遞給線程啟動的參數(shù)(IN)
_AFX_THREAD_STATE* pThreadState;//父線程的線程狀態(tài)
CWinThread* pThread; //新創(chuàng)建的MFC線程對象
DWORD dwCreateFlags; //線程創(chuàng)建標識
_PNH pfnNewHandler; //新線程的句柄
HANDLE hEvent; //同步事件,線程創(chuàng)建成功或失敗后置位
HANDLE hEvent2; //同步事件,新線程恢復執(zhí)行后置位
//返回給創(chuàng)建線程的參數(shù),在新線程恢復執(zhí)行后賦值
BOOL bError; //如果創(chuàng)建發(fā)生錯誤,TRUE
};
該結構作為線程開始函數(shù)的參數(shù)被傳遞給_beginthreadex函數(shù)來創(chuàng)建和啟動線程。_beginthreadex函數(shù)是“C”的線程創(chuàng)建函數(shù),具有如下原型:
unsigned long _beginthreadex(
void *security,
unsigned stack_size,
unsigned ( __stdcall *start_address )( void * ),
void *arglist,
unsigned initflag,
unsigned *thrdaddr );
圖8-2描述了上述過程。圖中表示,_AfxThreadEntry在啟動線程時,將創(chuàng)建本線程的線程狀態(tài),并且繼承父線程的模塊狀態(tài)。關于MFC狀態(tài),見第9章。
從圖8-2可以看出,AfxEndThread用來結束調用它的線程:它將清理本線程創(chuàng)建的MFC對象和釋放線程局部存儲分配的內存空間;調用CWinThread的虛擬函數(shù)Delete;調用“C”的結束線程函數(shù)_endthreadex釋放分配給線程的資源,但是不關閉線程句柄。
CWinThread::Delete的缺省實現(xiàn)是:如果本線程的成員函數(shù)m_bDelete為TRUE,則調用“C”運算符號delete銷毀MFC線程對象自身(delete this),這將導致線程對象的析構函數(shù)被調用。若析構函數(shù)檢測線程句柄非空則調用CloseHandle關閉它。
通常,讓m_bDelete為TRUE以便自動地銷毀線程對象,釋放內存空間(MFC內存對象在堆中分配)。但是,有時候,在線程結束之后(Win32線程已經(jīng)不存在)保留MFC線程對象是有用的,當然程序員自己最后要記得銷毀該線程對象。
在MFC中,消息循環(huán)是由線程完成的。一般地,可以使用MFC缺省的消息循環(huán)(即使用函數(shù)CWindThrad::Run),但是,有些時候需要程序員自己實現(xiàn)一個線程的消息循環(huán),比如在用戶界面線程進行一個長時間計算處理或者等待另一個線程時。一般有如下形式:
while ( bDoingBackgroundProcessing)
{
MSG msg;
while ( ::PeekMessage( &msg, NULL,0, 0, PM_NOREMOVE ) )
{
if ( !PumpMessage( ) )
{
bDoingBackgroundProcessing = FALSE;
::PostQuitMessage( );
break;
}
}
// let MFC do its idle processing
LONG lIdle = 0;
while ( AfxGetApp()->OnIdle(lIdle++ ) );
// Perform some background processing here
// using another call to OnIdle
}
該段代碼的解釋參見圖5-3對線程的Run函數(shù)的圖解。
程序員實現(xiàn)線程的消息循環(huán)有兩個好處,一是顧及了MFC的Idle處理機制;二是在長時間的處理中可以響應用戶產(chǎn)生的事件或者消息。
在同步對象上等待其他線程時,也可以使用同樣的方式,只要把條件
bDoingBackgroundProcessing
換成如下形式:
WaitForSingObject(hHandleOfEvent,0) == WAIT_TIMEOUT
即可。
MFC處理線程和進程時還引入了一個重要的概念:狀態(tài),如線程狀態(tài)(Thread State)、進程狀態(tài)(Process State)、模塊狀態(tài)(Module State)等。由于這個概念在MFC中占有重要地位,涉及的內容比較多,所以專門在下一章來講述它。