Windows下多線程編程技術及其實現

Windows下多線程編程技術及其實現

  本文首先討論16位Windows下不具備的線程的概念,然後着重講述在32位Windows 95環境下多線程的編程技術,最後給出利用該技術的一個實例,即基於Windows95下TCP/IP的可視電話的實現。

  一、問題的提出

  作者最近在開發基於Internet網上的可視電話過程中,碰到了這樣一個問題。在基於Internet網上的可視電話系統中,同時要進行語音採集、語音編解碼、圖象採集、圖象編解碼、語音和圖象 碼流的傳輸, 所有的這些事情,都要並行處理。特別是語音信號,如果進行圖象編解碼時間過長,語音信號得不到服務,通話就有間斷,如果圖象或語音處理時間過長,而不能及時的傳輸碼流數據,通信同樣也會中斷。這樣就要求我們實現一種並行編程,在只有一個CPU的機器上,也就是要將該CPU時間按照一定的優先準則分配給各個事件,定期處理某一事件而不會在某一事件處理過長,在32位Windows95或WindowsNT下,我們可以用多線程的編程技術來實現這種並行編程。實際上這種並行編程在很多場合下都是必須的。例如,在FileManager拷貝文件時,它顯示一個對話框, 列出源文件和目標文件的名稱,並在對話框中包含了一個Cancel按鈕。如果在文件拷貝過程中,點中Cancel按鈕,就會終止拷貝。

  在16位Windows中,實現這類功能需要在FileCopy循環內部週期性地調用PeekMessage函數。如果正在讀一個很大的數據塊,則只有當這個塊讀完以後才能響應這個按鈕動作,如果從軟盤讀文件,則要花費好幾秒的時間,由於機器反應太遲鈍,你會頻繁地點中這個按鈕,以爲系統不知道你想終止這個操作。如果把FileCopy指令放入另外一個線程,你就不需要在代碼中放一大堆PeekMessage函數,處理 用戶界面的線程將與它分開操作,這樣,點中Cancel按鈕後會立即得到響應。同樣的道理,在應用程序中創建一個單獨線程來處理所有打印任務也是很有用的,這樣,用戶可以在打印處理時繼續使用應用程序。

  二、線程的概念

  爲了瞭解線程的概念,我們必須先討論一下進程的概念。

  一個進程通常定義爲程序的一個實例。在Win32中, 進程佔據4GB的地址空間。與它們在MS-DOS和16位Windows操作系統中不同, Win32進程是沒有活力的。這就是說,一個Win32進程並不執行什麼指令,它只是佔據着4GB的地址空間,此空間中有應用程序EXE文件的 代碼和數據。EXE需要的任意DLL也將它們的代碼和數據裝入到進程的地址空間。除了地址空間,進程還佔有某些資源,比如文件、動態內存分配和線程。當進程終止時,在它生命期中創建的各種資源將被清除。

  但是進程是沒有活力的,它只是一個靜態的概念。爲了讓進程完成一些工作,進程必須至少佔有一個線程,所以線程是描述進程內的執行,正是線程負責執行包含在進程的地址空間中的代碼。實際上,單個進程可以包含幾個線程, 它們可以同時執行進程的地址空間中的代碼。爲了做到這一點,每個線程有自己的一組CPU寄存器和堆棧。

  每個進程至少有一個線程在執行其地址空間中的代碼,如果沒有線程執行進程 地址空間中的代碼, 進程也就沒有繼續存在的理由,系統將自動清除進程及其地址空間。爲了運行所有這些線程,操作系統爲每個獨立線程安排一些CPU 時間,操作系統以輪轉方式向線程提供時間片,這就給人一種假象,好象這些線程都在同時運行。創建一個Win32進程時,它的第一個線程稱爲主線程,它 由系統自動生成,然後可由這個主線程生成額外的線程,這些線程,又可生成更多的線程。

  三、線程的編程技術

  1、編寫線程函數

  所有線程必須從一個指定的函 數開始執行,該函數稱爲線程函數,它必須具有下列原型:

DWORD WINAPIYourThreadFunc(LPVOIDlpvThreadParm);

  該函數輸入一個LPVOID型的參數,可以是一個DWORD型的整數,也可以是一個指向一個緩衝區的指針, 返回一個DWORD型的值。象WinMain函數一樣,這個函數並不由操作系統調用, 操作系統調用包含在KERNEL32.DLL中的非C運行時的一個內部函數,如StartOfThread,然後由StartOfThread函數建立起一個異常處理框架後,調用我們的函數。

  2、創建一個線程

  一個進程的主線程是由操作系統自動生成,如果你要讓一個主線程創建額外的線程,你可以調用來CreateThread完成。

  HANDLE  CreateThread(LPSECURITY_ATTRIBUTES lpsa,DWORDcbstack,LPTHREAD_START_ROUTINElpStartAddr,
LPVOID lpvThreadParm,DWORDfdwCreate,LPDWORDlpIDThread);

  其中lpsa參數爲一個指向SECURITY_ATTRIBUTES結構的指針。如果想讓對象爲缺省安全屬性的話,可以傳一個NULL,如果想讓任一個子進程都可繼承一個該線程對象句柄,必須指定一個SECURITY_ATTRIBUTES結構,其中bInheritHandle成員初始化爲TRUE。參數cbstack表示線程爲自己所用堆棧分配的地址空間大小,0表示採用系統缺省值。

  參數lpStartAddr用來表示新線程開始執行時代碼所在函數的地址,即爲線程函數。lpvThreadParm爲傳入線程函數的參數,fdwCreate參數指定控制線程創建的附加標誌,可以取兩種值。如果該參數爲0,線程就會立即開始執行,如果該參數爲CREATE_SUSPENDED,則系統產生線程後,初始化CPU,登記CONTEXT結構的成員,準備好執行該線程函數中的第一條指令,但並不馬上執行,而是掛起該線程。最後一個參數lpIDThread 是一個DWORD類型地址,返回賦給該新線程的ID值。

  3、終止線程

  如果某線程調用了ExitThread 函數,就可以終止自己。

                VOID  ExitThread(UINTfuExitCode );

  這個函數爲調用該函數的線程設置了退出碼fuExitCode後, 就終止該線程。調用TerminateThread函數亦可終止線程。

BOOLTerminateThread(HANDLE hThread,DWORDdwExitCode);

  該函數用來結束由hThread參數指定的線程, 並把dwExitCode設成該線程的退出碼。當某個線程不在響應時,我們可以用其他線程調用該函數來終止這個不響應的線程。

  4、設定線程的相對優先級

  當一個線程被首次創建時,它的優先級等同於它所屬進程的優先級。在單個進程內可以通過調用SetThreadPriority函數改變線程的相對優先級。一個線程的優先級是相對於其所屬的進程的優先級而言的。

                     BOOL   SetThreadPriority(HANDLE hThread,intnPriority);

  其中參數hThread是指向待修改 優先級線程的句柄,nPriority可以是以下的值:

  THREAD_PRIORITY_LOWEST,
  THREAD_PRIORITY_BELOW_NORMAL,
  THREAD_PRIORITY_NORMAL,
  THREAD_PRIORITY_ABOVE_NORMAL,
  THREAD_PRIORITY_HIGHEST

  5、掛起及恢復線程

  先前我提到過可以創建掛起狀態的線程(通過傳遞CREATE_SUSPENDED標誌給函數CreateThread來實現)。當你這樣做時,系統創建指定線程的核心對象,創建線程的棧,在CONTEXT結構中初始化線程CPU註冊成員。然而,線程對象被分配了一個初始掛起計數值1,這表明了系統將不再分配CPU去執行線程。要開始執行一個線程,另一個線程必須調用ResumeThread並傳遞給它調用CreateThread時返回的線程句柄。

                                 DWORD ResumeThread(HANDLEhThread);

  一個線程可以被掛起多次。如果一個線程被掛起3次, 則該線程在它被分配CPU之前必須被恢復3次。除了在創建線程時使用CREATE_SUSPENDED標誌,你還可以用SuspendThread函數掛起線程。

                                 DWORD SuspendThread(HANDLE hThread);  

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章