如上所述,進程是沒有活力的,它只是一個靜態的概念。為了讓進程完成一些工作,進程必須至少占有一線程,所以線程是描述進程內的執行,正是線程負責執行包含在進程的地址空間中的代碼。實際上,單個進程可能包含幾個線程,它們可以同時執行進程的地址空間中的代碼。為了做到這一點,每個線程有自己的一組CPU寄存器和椎。每個進程至少有一個線址程在執行其地址空間中的代碼,如果沒有線程執行進程地空間中的代碼,如果沒有線程執行進程地址空間中的代碼,進程也就沒有繼續存在的理由,系統將自動清除進程及其地址空間。為了運行所有這些線程,操作系統為每個獨立線程安排一些CPU時間,操作系統以輪轉方式向線程提供時間片,這就給人一種假象,好象這些線程都在同時運行。創建一個32位Windows進程時,它的第一個線程稱為主線程,由系統自動生成,然後可由這個主線程生成額外的線程,這些線程又可生成更多的線程。
例如,在基於Internet網上的可視電話系統中,同時要進行語音采集、語音編譯碼、圖像采集、圖像編譯碼、語音和圖像碼流的傳輸,所有這些工作,都要並行處理。特別是語音信號,如果進行圖像編解碼時間過長,語音信號得不到服務,通話就有間斷;如果圖像或語音處理時間過長,而不能及時傳輸碼流數據,通信同樣也會中斷。這樣就要求我們實現一種並行編程,在只有一個CPU的機器上,也就是要將該CPU時間按時一定的優先准則分配給各個事件,定期處理各事件,而不會對某一事件處理過長。
在32位Windows95或Windows NT下,我們可以用多線程的處理技術來實現這種並行處理。實際上,這種並行編程在很多場合下都是必須的。再例如,在File Manager拷貝文件時,它顯示一個對話框中包含了一個Cancel按鈕。如果在文件拷貝過程中,點中Cance l按鈕,就會終止拷貝。在16位Winows中,實現這類功能需要在File Copy循環內部周期性地調用PeekMessage函數。如果正在讀一個很大的動作;如果從軟盤讀文件,則要花費好幾秒的時間。由於機器反應太遲鈍,用戶會頻繁地點中這個按鈕,以為系統不知道想終止這個操作。如果把File Copy指令放入另外一個線程,就不需要在代碼中放一大堆PeekMessage函數,處理用戶界面的線程將與它分開操作,點中Cancel按鈕後會立即得到響應。同樣的道理,在應用程序中創建一個單獨線程來處理所有打印任務也是很有用的,用戶可以在打印處理時繼續使用應用程序。
多線程的編程在Win32方式下和MFC類庫支持下的原理是一致的,進程的主線程在任何需要的時候都可以創建新的線程,當線程執行完任務後,自動終止線程,當進程結束後,所有的線程都終止。所有活動的線程共享進程的資源,所以在編程時,需要考慮在多個線程訪問同一資源時,產生沖突的問題,當一個線程正在訪問一個進程對象,這時另一個線程要改變該對象,這時可能會產生錯誤的結果,所以程序員在編程時要解決這種沖突。
下面舉例說明在Win32 基礎上進行多線程編程的過程。
1.使用函數說明
Win32函數庫裡提供了多線程控制的操作函數,包括創建線程、終止線程、建立互斥區等。首先,在應用程序的主線程或者其它活動線程的適當的地方創建新的線程,創建線程的函數如下:
HANDLE CreateThread(LPSECURITY_ATTRIBUTES lpThreadAttributes,
DWord dwStackSize, LPTHREAD_START_ROUTINE lpStartAddress,
LPVOID lpParameter, DWORD dwCreationFlags, LPDWord lpThreadId );
參數lpThreadAttributes 指定了線程的安全屬性,在Windows 95中被忽略;
參數dwStackSize 指定了線程的堆棧深度;
參數lpStartAddress 指定了線程的起始地址,一般情況為下面原型的函數
DWord WINAPI ThreadFunc( LPVOID );
參數 lpParameter指定了線程執行時傳送給線程的32位參數,即上面函數的參數;
參數dwCreationFlags指定了線程創建的特性;
參數 lpThreadId 指向一個DWord變量,可返回線程ID值。
如果創建成功則返回線程的句柄,否則返回NULL。
創建了新的線程後,線程開始啟動執行,如果在dwCreationFlags中用了CREATE_SUSPENDED特性,那麼線程並不馬上執行,而是先掛起,等到調用ResumeThread後才開始啟動線程,在這過程中可以調用函數
BOOL SetThreadPriority( HANDLE hThread, int nPriority);
設置線程的優先權。
當線程的函數返回後,線程自動終止,如果要想在線程的執行過程中終止的話,可以調用函數
VOID ExitThread( DWord dwExitCode);
如果在線程的外面終止線程的話,可以用下面的函數
BOOL TerminateThread( HANDLE hThread, DWord dwExitCode );
但注意,該函數可能會引起系統不穩定,而且,線程所占用的資源也不釋放,因此,一般情況下,建議不要使用該函數。
如果要終止的線程是進程內的最後一個線程的話,在線程被終止後,相應的進程也終止。
2. 無優先級例程,該例程實現在對話框上通過一線程實現時鐘的顯示和停止。步驟如下:
第一步:建立一基於對話框的工程MultiProcess1。
第二步:在對話框上建立兩個按鈕和一個編輯框,ID號分別為ID_START、ID_STOP和IDC_TIME,Caption分別為"啟動"、"停止"。如下:
第三步:在MultiProcess1Dlg.cpp中定義全局函數ThreadProc(),格式如下:
void ThreadProc()
{
CTime time;
CString m_time;
for(;;)
{
time=CTime::GetCurrentTime();
m_time=time.Format("%H:%M:%S");
::SetDlgItemText(AfxGetMainWnd()->m_hWnd,IDC_TIME,m_time);
Sleep(1000);
}
}
第四步:在頭文件MultiProcess1Dlg.h中定義變量如下:
DWord ThreadID;
HANDLE hThread;
第五步:雙擊"開始"按鈕,生成消息映射函數OnStart(),編寫其中的代碼如下:
void CMultiProcess1Dlg::OnStart()
{
// TODO: Add your control notification handler code here
hThread=CreateThread(NULL,0,(LPTHREAD_START_ROUTINE)ThreadProc,
NULL,0,&ThreadID);
}
此時即刻實現在對話框上點擊"啟動",啟動時鐘。接下來我們實現如何讓時鐘停下來。
第六步:雙擊"停止"按鈕,添加停止的消息映射函數OnStop(),編寫代碼如下:
void CMultiProcess1Dlg::OnStop()
{
// TODO: Add your control notification handler code here
TerminateThread(hThread,1);
}
注意:該函數可能會引起系統不穩定,而且,線程所占用的資源也不釋放,因此,一般情況下,建議不要使用該函數。
到現在,這個程序就完整了,看一下效果吧!
最後需要說明的是,並不是設計多線程就是一個好的程序。目前大多數的計算機都是單處理器(CPU)的,在這種機器上運行多線程程序,有時反而會降低系統性能,如果兩個非常活躍的線程為了搶奪對CPU的控制權,在線程切換時會消耗很多的CPU資源,但對於大部分時間被阻塞的線程(例如等待文件 I/O 操作),可以用一個單獨的線程來完成,這樣可把CPU時間讓出來,使程序獲得更好的性能。因此,在設計多線程應用時,需要慎重選擇,具體情況具體處理,以使應用程序獲得最佳的性能。