天天看點

深入淺出Win32多線程程式設計(一)――基本概念

<b>作者:</b>宋寶華<b> </b><b> </b><b>e-mail:</b>[email protected]<b></b>

從單程序單線程到多程序多線程是作業系統發展的一種必然趨勢,當年的DOS系統屬于單任務作業系統,最優秀的程式員也隻能通過駐留記憶體的方式實作所謂的“多任務”,而如今的Win32作業系統卻可以一邊聽音樂,一邊程式設計,一邊列印文檔。

了解多線程及其同步、互斥等通信方式是了解現代作業系統的關鍵一環,當我們精通了Win32多線程程式設計後,了解和學習其它作業系統的多任務控制也非常容易。許多程式員從來沒有學習過嵌入式系統領域著名的作業系統VxWorks,但是立馬就能在上面做開發,大概要歸功于平時在Win32多線程上下的功夫。

是以,學習Win32多線程不僅對了解Win32本身有重要意義,而且對學習和領會其它作業系統也有觸類旁通的作用。

先闡述一下程序和線程的概念和差別,這是一個許多大學老師也講不清楚的問題。

程序(Process) 是具有一定獨立功能的程式關于某個資料集合上的一次運作活動,是系統進行資源配置設定和排程的一個獨立機關。程式隻是一組指令的有序集合,它本身沒有任何運作 的含義,隻是一個靜态實體。而程序則不同,它是程式在某個資料集上的執行,是一個動态實體。它因建立而産生,因排程而運作,因等待資源或事件而被處于等待 狀态,因完成任務而被撤消,反映了一個程式在一定的資料集上運作的全部動态過程。

線程(Thread)是程序的一個實體,是CPU排程和分派的基本機關。線程不能夠獨立執行,必須依存在應用程式中,由應用程式提供多個線程執行控制。

線 程和程序的關系是:線程是屬于程序的,線程運作在程序空間内,同一程序所産生的線程共享同一記憶體空間,當程序退出時該程序所産生的線程都會被強制退出并清 除。線程可與屬于同一程序的其它線程共享程序所擁有的全部資源,但是其本身基本上不擁有系統資源,隻擁有一點在運作中必不可少的資訊(如程式計數器、一組寄存器和棧)。

根據程序與線程的設定,作業系統大緻分為如下類型:

(1)單程序、單線程,MS-DOS大緻是這種作業系統;

(2)多程序、單線程,多數UNIX(及類UNIX的LINUX)是這種作業系統;

(3)多程序、多線程,Win32(Windows NT/2000/XP等)、Solaris 2.x和OS/2都是這種作業系統;

(4)單程序、多線程,VxWorks是這種作業系統。

在作業系統中引入線程帶來的主要好處是:

(1)在程序内建立、終止線程比建立、終止程序要快;

(2)同一程序内的線程間切換比程序間的切換要快,尤其是使用者級線程間的切換。另外,線程的出現還因為以下幾個原因:

(1)并發程式的并發執行,在多處理環境下更為有效。一個并發程式可以建立一個程序,而這個并發程式中的若幹并發程式段就可以分别建立若幹線程,使這些線程在不同的處理機上執行。

(2)每個程序具有獨立的位址空間,而該程序内的所有線程共享該位址空間。這樣可以解決父子程序模型中,子程序必須複制父程序位址空間的問題。

(3)線程對解決客戶/伺服器模型非常有效。

<b>3.1</b><b>程序間通信(</b><b>IPC</b><b>)</b><b></b>

Win32程序間通信的方式主要有:

(1)剪貼闆(Clip Board);

(2)動态資料交換(Dynamic Data Exchange);

(3)部件對象模型(Component Object Model);

(4)檔案映射(File Mapping);

(5)郵件槽(Mail Slots);

(6)管道(Pipes);

(7)Win32套接字(Socket);

(8)遠端過程調用(Remote Procedure Call);

(9)WM_COPYDATA消息(WM_COPYDATA Message)。

<b>3.2</b><b>擷取程序資訊</b><b></b>

在WIN32中,可使用在PSAPI .DLL中提供的Process status Helper函數幫助我們擷取程序資訊。

(1)EnumProcesses()函數可以擷取程序的ID,其原型為:

BOOL EnumProcesses(DWORD * lpidProcess, DWORD cb, DWORD*cbNeeded);

參數lpidProcess:一個足夠大的DWORD類型的數組,用于存放程序的ID值;

參數cb:存放程序ID值的數組的最大長度,是一個DWORD類型的資料;

參數cbNeeded:指向一個DWORD類型資料的指針,用于傳回程序的數目;

函數傳回值:如果調用成功,傳回TRUE,同時将所有程序的ID值存放在lpidProcess參數所指向的數組中,程序個數存放在cbNeeded參數所指向的變量中;如果調用失敗,傳回FALSE。

(2)GetModuleFileNameExA()函數可以實作通過程序句柄擷取程序檔案名,其原型為:

DWORD GetModuleFileNameExA(HANDLE hProcess, HMODULE hModule,

LPTSTR lpstrFileName, DWORD nsize);

參數hProcess:接受程序句柄的參數,是HANDLE類型的變量;

參數hModule:指針型參數,在本文的程式中取值為NULL;

參數lpstrFileName:LPTSTR類型的指針,用于接受主調函數傳遞來的用于存放程序名的字元數組指針;

參數nsize:lpstrFileName所指數組的長度;

函數傳回值:如果調用成功,傳回一個大于0的DWORD類型的資料,同時将hProcess所對應的程序名存放在lpstrFileName參數所指向的數組中;加果調用失敗,則傳回0。

通過下列代碼就可以周遊系統中的程序,獲得程序清單:

//擷取目前程序總數

EnumProcesses(process_ids, sizeof(process_ids), &amp;num_processes);

//周遊程序

for (int i = 0; i &lt; num_processes; i++)

{

  //根據程序ID擷取句柄

  process[i] = OpenProcess(PROCESS_QUERY_INFORMATION | PROCESS_VM_READ, 0,

    process_ids[i]);

  //通過句柄擷取程序檔案名

  if (GetModuleFileNameExA(process[i], NULL, File_name, sizeof(fileName)))

    cout &lt;&lt; fileName &lt;&lt; endl;

}

WIN32靠線程的優先級(達到搶占式多任務的目的)及配置設定給線程的CPU時間來排程線程。WIN32本身的許多應用程式也利用了多線程的特性,如任務管理器等。

本質而言,一個處理器同一時刻隻能執行一個線程(“微觀串行”)。WIN32多任務機制使得CPU好像在同時處理多個任務一樣,實作了“宏觀并行”。其多線程排程的機制為:

(1)運作一個線程,直到被中斷或線程必須等待到某個資源可用;

(2)儲存目前執行線程的描述表(上下文);

(3)裝入下一執行線程的描述表(上下文);

(4)若存在等待被執行的線程,則重複上述過程。

WIN32下的線程可能具有不同的優先級,優先級的範圍為0~31,共32級,其中31表示最高優先級,優先級0為系統保留。它們可以分成兩類,即實時優先級和可變優先級:

(1)實時優先級從16到31,是實時程式所用的高優先級線程,如許多監控類應用程式;

(2)可變優先級從1到15,絕大多數程式的優先級都在這個範圍内。。WIN32排程器為了優化系統響應時間,在它們執行過程中可動态調整它們的優先級。

多線程确實給應用開發帶來了許多好處,但并非任何情況下都要使用多線程,一定要根據應用程式的具體情況來綜合考慮。一般來說,在以下情況下可以考慮使用多線程:

(1)應用程式中的各任務相對獨立;

(2)某些任務耗時較多;

(3)各任務需要有不同的優先級。

另外,對于一些實時系統應用,應考慮多線程。

WIN32核心對象包括程序、線程、檔案、事件、信号量、互斥體和管道,核心對象可能有不隻一個擁有者,甚至可以跨程序。有一組WIN32 API與核心對象息息相關:

(1)WaitForSingleObject,用于等待對象的“激活”,其函數原型為:

DWORD WaitForSingleObject(

  HANDLE hHandle,         // 等待對象的句柄

  DWORD dwMilliseconds    // 等待毫秒數,INFINITE表示無限等待

);

可以作為WaitForSingleObject第一個參數的對象包括:Change notification、Console input、Event、Job、Memory resource notification、Mutex、Process、Semaphore、Thread和Waitable timer。

如果等待的對象不可用,那麼線程就會挂起,直到對象可用線程才會被喚醒。對不同的對象,WaitForSingleObject表現為不同的含義。例如,使用WaitForSingleObject(hThread,…)可以判斷一個線程是否結束;使用WaitForSingleObject(hMutex,…)可以判斷是否能夠進入臨界區;而WaitForSingleObject (hProcess,… )則表現為等待一個程序的結束。

與WaitForSingleObject對應還有一個WaitForMultipleObjects函數,可以用于等待多個對象,其原型為:

DWORD WaitForMultipleObjects(

  DWORD nCount,

  const HANDLE* pHandles,

  BOOL bWaitAll,

  DWORD dwMilliseconds

(2)CloseHandle,用于關閉對象,其函數原型為:

BOOL CloseHandle(

  HANDLE hObject

如果函數執行成功,則傳回TRUE;否則傳回FALSE,我們可以通過GetLastError函數進一步可以獲得錯誤原因。

在VC++6.0中,有兩種多線程程式設計方法:一是使用C運作時庫及WIN32 API函數,另一種方法是使用MFC,MFC對多線程開發有強大的支援。

标準C運作時庫是1970年問世的,當時還沒有多線程的概念。是以,C運作時庫早期的設計者們不可能考慮到讓其支援多線程應用程式。

Visual C++提供了兩種版本的C運作時庫,—個版本供單線程應用程式調用,另一個版本供多線程應用程式調用。多線程運作時庫與單線程運作時庫有兩個重大差别:

(1)類似errno的全局變量,每個線程單獨設定一個;

     這樣從每個線程中可以擷取正确的錯誤資訊。

(2)多線程庫中的資料結構以同步機制加以保護。

這樣可以避免通路時候的沖突。

Visual C++提供的多線程運作時庫又分為靜态連結庫和動态連結庫兩類,而每一類運作時庫又可再分為debug版和release版,是以Visual C++共提供了6個運作時庫。如下表:

C運作時庫

庫檔案

Single thread(static link)

libc.lib

Debug single thread(static link)

Libcd.lib

MultiThread(static link)

libcmt.lib

Debug multiThread(static link)

libcmtd.lib

MultiThread(dynamic link)

msvert.lib

Debug multiThread(dynamic link)

msvertd.lib

如果不使用VC多線程C運作時庫來生成多線程程式,必須執行下列操作:

(1)使用标準 C 庫(基于單線程)并且隻允許可重入函數集進行庫調用;

(2)使用 Win32 API 線程管理函數,如 CreateThread;

(3)通過使用 Win32 服務(如信号量和 EnterCriticalSection 及 LeaveCriticalSection 函數),為不可重入的函數提供自己的同步。

 如果使用标準 C 庫而調用VC運作時庫函數,則在程式的link階段會提示如下錯誤:

error LNK2001: unresolved external symbol __endthreadex

error LNK2001: unresolved external symbol __beginthreadex

 本文轉自 21cnbao 51CTO部落格,原文連結:http://blog.51cto.com/21cnbao/120736,如需轉載請自行聯系原作者