程序師世界是廣大編程愛好者互助、分享、學習的平台,程序師世界有你更精彩!
首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫知識
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
 程式師世界 >> 編程語言 >> C語言 >> VC >> 關於VC++ >> VC++實現多線程的調度和處理

VC++實現多線程的調度和處理

編輯:關於VC++

Windows95 和WindowsNT 操作系統支持多任務調度和處理,基於該功能所提供的多任務空間,程序員可以完全控制應用程序中每一個片段的運行,從而編寫高效率的應用程序。

所謂多任務通常包括這樣兩大類:多進程和多線程。進程是指在系統中正在運行的一個應用程序;線程是系統分配處理器時間資源的基本單元,或者說進程之內獨立執行的一個單元。對於操作系統而言,其調度單元是線程。一個進程至少包括一個線程,通常將該線程稱為主線程。一個進程從主線程的執行開始進而創建一個或多個附加線程,就是所謂基於多線程的多任務。

開發多線程應用程序可以利用32 位Windows 環境提供的Win32 API 接口函數,也可以利用VC++ 中提供的MFC 類庫進行開發。多線程編程在這兩種方式下原理是一樣的,用戶可以根據需要選擇相應的工具。本文重點講述用VC++5.0 提供的MFC 類庫實現多線程調度與處理的方法以及由線程多任務所引發的同步多任務特征,最後詳細解釋一個實現多線程的例程。

二基於MFC 的多線程編程

1 MFC 對多線程的支持

MFC 類庫提供了多線程編程支持,對於用戶編程實現來說更加方便。非常重要的一點就是,在多窗口線程情況下,MFC 直接提供了用戶接口線程的設計。

MFC 區分兩種類型的線程:輔助線程(Worker Thread)和用戶界面線程(UserInterface Thread)。輔助線程沒有消息機制,通常用來執行後台計算和維護任務。MFC 為用戶界面線程提供消息機制,用來處理用戶的輸入,響應用戶產生的事件和消息。但對於Win32 的API 來說,這兩種線程並沒有區別,它只需要線程的啟動地址以便啟動線程執行任務。用戶界面線程的一個典型應用就是類CWinApp,大家對類CwinApp 都比較熟悉,它是CWinThread 類的派生類,應用程序的主線程是由它提供,並由它負責處理用戶產生的事件和消息。類CwinThread 是用戶接口線程的基本類。CWinThread 的對象用以維護特定線程的局部數據。因為處理線程局部數據依賴於類CWinThread,所以所有使用MFC 的線程都必須由MFC 來創建。例如,由run-time 函數_beginthreadex 創建的線程就不能使用任何MFC API。

2 輔助線程和用戶界面線程的創建和終止

要創建一個線程,需要調用函數AfxBeginThread。該函數通過參數重載具有兩種版本,分別對應輔助線程和用戶界面線程。無論是輔助線程還是用戶界面線程,都需要指定額外的參數以修改優先級,堆棧大小,創建標志和安全特性等。函數AfxBeginThread 返回指向CWinThread 類對象的指針。

創建助手線程相對簡單。只需要兩步:實現控制函數和啟動線程。它並不必須從CWinThread 派生一個類。簡要說明如下:

1. 實現控制函數。控制函數定義該線程。當進入該函數,線程啟動;退出時,線程終止。該控制函數聲明如下:

UINT MyControllingFunction( LPVOID pParam );

該參數是一個單精度32 位值。該參數接收的值將在線程對象創建時傳遞給構造函數。控制函數將用某種方式解釋該值。可以是數量值,或是指向包括多個參數的結構的指針,甚至可以被忽略。如果該參數是指結構,則不僅可以將數據從調用函數傳給線程,也可以從線程回傳給調用函數。如果使用這樣的結構回傳數據,當結果准備好的時候,線程要通知調用函數。當函數結束時,應返回一個UINT 類型的值值,指明結束的原因。通常,返回0 表明成功,其它值分別代表不同的錯誤。

2. 啟動線程。由函數AfxBeginThread 創建並初始化一個CWinThread 類的對象,啟動並返回該線程的地址。則線程進入運行狀態。

3. 舉例說明。下面用簡單的代碼說明怎樣定義一個控制函數以及如何在程序的其它部分使用。

UINT MyThreadProc( LPVOID pParam )
{
CMyObject* pObject = (CMyObject*)pParam;
if (pObject == NULL ||
!pObject- >IsKindOf(RUNTIME_CLASS(CMyObject)))
return -1; //非法參數
……//具體實現內容
return 0; //線程成功結束
}
//在程序中調用線程的函數
……
pNewObject = new CMyObject;
AfxBeginThread(MyThreadProc, pNewObject);
……

創建用戶界面線程有兩種方法。

第一種方法,首先從CWinTread 類派生一個類(注意必須要用宏DECLARE_DYNCREATE 和IMPLEMENT_DYNCREATE 對該類進行聲明和實現);然後調用函數AfxBeginThread 創建CWinThread 派生類的對象進行初始化啟動線程運行。除了調用函數AfxBeginThread 之外, 也可以采用第二種方法,即先通過構造函數創建類CWinThread 的一個對象,然後由程序員調用函數::CreateThread 來啟動線程。通常類CWinThread 的對象在該線程的生存期結束時將自動終止,如果程序員希望自己來控制,則需要將m_bAutoDelete 設為FALSE。這樣在線程終止之後類CWinThread 對象仍然存在,只是在這種情況下需要手動刪除CWinThread 對象。

通常線程函數結束之後,線程將自行終止。類CwinThread 將為我們完成結束線程的工作。如果在線程的執行過程中程序員希望強行終止線程的話,則需要在線程內部調用AfxEndThread(nExitCode)。其參數為線程結束碼。這樣將終止線程的運行,並釋放線程所占用的資源。如果從另一個線程來終止該線程,則必須在兩個線程之間設置通信方法。如果從線程外部來終止線程的話,還可以使用Win32 函數(CWinThread 類不提供該成員函數):BOOL TerminateThread(HANDLE hThread,DWORD dwExitcode)。但在實際程序設計中對該函數的使用一定要謹慎,因為一旦該命令發出,將立即終止該線程,並不釋放線程所占用的資源,這樣可能會引起系統不穩定。

如果所終止的線程是進程內的最後一個線程,則在該線程終止之後進程也相應終止。

3 進程和線程的優先級問題

在Windows95 和WindowsNT 操作系統當中,任務是有優先級的,共有32 級,從0 到31,系統按照不同的優先級調度線程的運行。

1) 0-15 級是普通優先級,線程的優先級可以動態變化。高優先級線程優先運行,只有高優先級線程不運行時,才調度低優先級線程運行。優先級相同的線程按照時間片輪流運行。2) 16-30 級是實時優先級,實時優先級與普通優先級的最大區別在於相同優先級進程的運行不按照時間片輪轉,而是先運行的線程就先控制CPU,如果它不主動放棄控制,同級或低優先級的線程就無法運行。

一個線程的優先級首先屬於一個類,然後是其在該類中的相對位置。線程優先級的計算可以如下式表示:

線程優先級= 進程類基本優先級+ 線程相對優先級

進程類的基本優先級:

IDLE_PROCESS_CLASS
NORMAL_PROCESS_CLASS
HIGH_PROCESS_CLASS
REAL_TIME_PROCESS_CLASS

線程的相對優先級:

THREAD_PRIORITY_IDLE
(最低優先級,僅在系統空閒時執行)
THREAD_PRIORITY_LOWEST
THREAD_PRIORITY_BELOW_NORMAL
THREAD_PRIORITY_NORMAL (缺省)
THREAD_PRIORITY_ABOVE_NORMAL
THREAD_PRIORITY_HIGHEST
THREAD_PRIORITY_CRITICAL
(非常高的優先級)

4 線程同步問題

編寫多線程應用程序的最重要的問題就是線程之間的資源同步訪問。因為多個線程在共享資源時如果發生訪問沖突通常會產生不正確的結果。例如,一個線程正在更新一個結構的內容的同時另一個線程正試圖讀取同一個結構。結果,我們將無法得知所讀取的數據是什麼狀態:舊數據,新數據,還是二者的混合?

MFC 提供了一組同步和同步訪問類來解決這個問題,包括:

同步對象:CSyncObject, CSemaphore, CMutex, CcriticalSection 和CEvent ;同步訪問對象:CMultiLock 和CSingleLock 。

同步類用於當訪問資源時保證資源的整體性。其中CsyncObject 是其它四個同步類的基類,不直接使用。信號同步類CSemaphore 通常用於當一個應用程序中同時有多個線程訪問一個資源(例如,應用程序允許對同一個Document 有多個View)的情況;事件同步類CEvent 通常用於在應用程序訪問資源之前應用程序必須等待(比如,在數據寫進一個文件之前數據必須從通信端口得到)的情況;而對於互斥同步類CMutex 和臨界區同步類CcriticalSection 都是用於保證一個資源一次只能有一個線程訪問,二者的不同之處在於前者允許有多個應用程序使用該資源(例如,該資源在一個DLL 當中)而後者則不允許對同一個資源的訪問超出進程的范疇,而且使用臨界區的方式效率比較高。

同步訪問類用於獲得對這些控制資源的訪問。CMultiLock 和CSingleLock 的區別僅在於是需要控制訪問多個還是單個資源對象。

5 同步類的使用方法

解決同步問題的一個簡單的方法就是將同步類融入共享類當中,通常我們把這樣的共享類稱為線程安全類。下面舉例來說明這些同步類的使用方法。比如,一個用以維護一個帳戶的連接列表的應用程序。該應用程序允許3 個帳戶在不同的窗口中檢測,但一次只能更新一個帳戶。當一個帳戶更新之後,需要將更新的數據通過網絡傳給一個數據文檔。

該例中將使用3 種同步類。由於允許一次檢測3 個帳戶,使用CSemaphore 來限制對3 個視窗對象的訪問。當更新一個帳目時,應用程序使用CCriticalSection 來保證一次只有一個帳目更新。在更新成功之後,發CEvent 信號,該信號釋放一個等待接收信號事件的線程。該線程將新數據傳給數據文檔。

要設計一個線程安全類,首先根據具體情況在類中加入同步類做為數據成員。在例子當中,可以將一個CSemaphore 類的數據成員加入視窗類中,一個CCriticalSection 類數據成員加入連接列表類,而一個CEvent 數據成員加入數據存儲類中。

然後,在使用共享資源的函數當中,將同步類與同步訪問類的一個鎖對象聯系起來。即,在訪問控制資源的成員函數中應該創建一個CSingleLock 或CMultiLock 的對象並調用該對象的Lock 函數。當訪問結束之後,調用UnLock 函數,釋放資源。

用這種方式來設計線程安全類比較容易。在保證線程安全的同時,省去了維護同步代碼的麻煩,這也正是OOP 的思想。但是使用線程安全類方法編程比不考慮線程安全要復雜,尤其體現在程序調試過程中。而且線程安全編程還會損失一部分效率,比如在單CPU 計算機中多個線程之間的切換會占用一部分資源。

三編程實例

下面以VC++5.0 中一個簡單的基於對話框的MFC 例程來說明實現多線程任務調度與處理的方法,下面加以詳細解釋。

在該例程當中定義兩個用戶界面線程,一個顯示線程(CDisplayThread) 和一個計數線程(CCounterThread)。這兩個線程同時操作一個字符串變量m_strNumber,其中顯示線程將該字符串在一個列表框中顯示,而計數線程則將該字符串中的整數加1。在例程中,可以分別調整進程、計數線程和顯示線程的優先級。例程中的同步機制使用CMutex 和CSingleLock 來保證兩個線程不能同時訪問該字符串。同步機制執行與否將明顯影響程序的執行結果。在該例程中允許將將把兩個線程暫時掛起,以查看運行結果。例程中還允許查看計數線程的運行。該例程中所處理的問題也是多線程編程中非常具有典型意義的問題。

在該程序執行時主要有三個用於調整優先級的組合框,三個分別用於選擇同步機制、顯示計數線程運行和掛起線程的復選框以及一個用於顯示運行結果的列表框。

在本程序中使用了兩個線程類CCounterThread 和CDisplayThread,這兩個線程類共同操作定義在CMutexesDlg 中的字符串對象m_strNumber。本程序對同步類CMutex 的使用方法就是按照本文所講述的融入的方法來實現的。同步訪問類CSingleLock 的鎖對象則在各線程的具體實現中定義。

下面介紹該例程的具體實現:

利用AppWizard 生成一個名為Mutexes 基於對話框的應用程序框架。

利用對話框編輯器在對話框中填加以下內容:三個組合框,三個復選框和一個列表框。三個組合框分別允許改變進程優先級和兩個線程優先級,其ID 分別設置為:IDC_PRIORITYCLASS、IDC_DSPYTHRDPRIORITY 和IDC_CNTRTHRDPRIORITY。三個復選框分別對應著同步機制選項、顯示計數線程執行選項和暫停選項,其ID 分別設置為IDC_SYNCHRONIZE、IDC_SHOWCNTRTHRD 和IDC_PAUSE。列表框用於顯示線程顯示程序中兩個線程的共同操作對象m_strNumber,其ID 設置為IDC_DATABOX。

創建類CWinThread 的派生類CExampleThread。該類將作為本程序中使用的兩個線程類:CCounterThread 和CDisplayThread 的父類。這樣做的目的僅是為了共享兩個線程類的共用變量和函數。

在CExampleThread 的頭文件中填加如下變量:

CMutexesDlg * m_pOwner;//指向類CMutexesDlg指針

BOOL m_bDone;//用以控制線程執行

及函數:

void SetOwner(CMutexesDlg* pOwner)

{ m_pOwner=pOwner; };//取類CMutexesDlg的指針

然後在構造函數當中對成員變量進行初始化:

m_bDone=FALSE;//初始化允許線程運行

m_pOwner=NULL;//將該指針置為空

m_bAutoDelete=FALSE;//要求手動刪除線程對象

創建兩個線程類CCounterThread 和CdisplayThread。這兩個線程類是CExampleThread 的派生類。分別重載兩個線程函數中的::Run() 函數,實現各線程的任務。在這兩個類當中分別加入同步訪問類的鎖對象sLock,這裡將根據同步機制的復選與否來確定是否控制對共享資源的訪問。不要忘記需要加入頭文件#include "afxmt.h"。

計數線程::Run() 函數的重載代碼為:

int CCounterThread::Run()
{
BOOL fSyncChecked;//同步機制復選檢測
unsigned int nNumber;//存儲字符串中整數
if (m_pOwner == NULL)
return -1;
//將同步對象同鎖對象聯系起來
CSingleLock sLock(&(m_pOwner- >m_mutex));
while (!m_bDone)//控制線程運行,為終止線程服務
{
//取同步機制復選狀態
fSyncChecked = m_pOwner- >
IsDlgButtonChecked(IDC_SYNCHRONIZE);
//確定是否使用同步機制
if (fSyncChecked)
sLock.Lock();
//讀取整數
_stscanf((LPCTSTR) m_pOwner- >m_strNumber,
_T("%d"), &nNumber);
nNumber++;//加1
m_pOwner- >m_strNumber.Empty();//字符串置空
while (nNumber != 0) //更新字符串
{
m_pOwner- >m_strNumber +=
(TCHAR) ('0'+nNumber%10);
nNumber /= 10;
}
//調整字符串順序
m_pOwner- >m_strNumber.MakeReverse();
//如果復選同步機制,釋放資源
if (fSyncChecked)
sLock.Unlock();
//確定復選顯示計數線程
if (m_pOwner- >IsDlgButtonChecked(IDC_SHOWCNTRTHRD))
m_pOwner- >AddToListBox(_T("Counter: Add 1"));
}//結束while
m_pOwner- >PostMessage(WM_CLOSE, 0, 0L);
return 0;
}

顯示線程的::Run()函數重載代碼為:

int CDisplayThread::Run()
{
BOOL fSyncChecked;
CString strBuffer;
ASSERT(m_pOwner != NULL);
if (m_pOwner == NULL)
return -1;
CSingleLock sLock(&(m_pOwner- >m_mutex));
while (!m_bDone)
{
fSyncChecked = m_pOwner- >
IsDlgButtonChecked(IDC_SYNCHRONIZE);
if (fSyncChecked)
sLock.Lock();
//構建要顯示的字符串
strBuffer = _T("Display: ");
strBuffer += m_pOwner- >m_strNumber;
if (fSyncChecked)
sLock.Unlock();
//將字符串加入到列表框中
m_pOwner- >AddToListBox(strBuffer);
}//結束while
m_pOwner- >PostMessage(WM_CLOSE, 0, 0L);
return 0;
}

3 在CMutexesDlg的頭文件中加入如下成員變量:

CString m_strNumber;//線程所要操作的資源對象

CMutex m_mutex;//用於同步機制的互斥量

CCounterThread* m_pCounterThread;//指向計數線程的指針

CDisplayThread* m_pDisplayThread;//指向顯示線程的指針

首先在對話框的初始化函數中加入如下代碼對對話框進行初始化:

BOOL CMutexesDlg::OnInitDialog()
{
……
//初始化進程優先級組合框並置缺省為NORMAL
CComboBox* pBox;
pBox = (CComboBox*) GetDlgItem(IDC_PRIORITYCLASS);
ASSERT(pBox != NULL);
if (pBox != NULL){
pBox- >AddString(_T("Idle"));
pBox- >AddString(_T("Normal"));
pBox- >AddString(_T("High"));
pBox- >AddString(_T("Realtime"));
pBox- >SetCurSel(1);
}
//初始化顯示線程優先級組合框並置缺省為NORMAL
pBox = (CComboBox*) GetDlgItem(IDC_DSPYTHRDPRIORITY);
ASSERT(pBox != NULL);
if (pBox != NULL){
pBox- >AddString(_T("Idle"));
pBox- >AddString(_T("Lowest"));
pBox- >AddString(_T("Below normal"));
pBox- >AddString(_T("Normal"));
pBox- >AddString(_T("Above normal"));
pBox- >AddString(_T("Highest"));
pBox- >AddString(_T("Timecritical"));
pBox- >SetCurSel(3);
}
//初始化計數線程優先級組合框並置缺省為NORMAL
pBox = (CComboBox*) GetDlgItem(IDC_CNTRTHRDPRIORITY);
ASSERT(pBox != NULL);
if (pBox != NULL){
pBox- >AddString(_T("Idle"));
pBox- >AddString(_T("Lowest"));
pBox- >AddString(_T("Below normal"));
pBox- >AddString(_T("Normal"));
pBox- >AddString(_T("Above normal"));
pBox- >AddString(_T("Highest"));
pBox- >AddString(_T("Timecritical"));
pBox- >SetCurSel(3);
}
//初始化線程掛起復選框為掛起狀態
CButton* pCheck = (CButton*) GetDlgItem(IDC_PAUSE);
pCheck- >SetCheck(1);
//初始化線程
m_pDisplayThread = (CDisplayThread*)
AfxBeginThread(RUNTIME_CLASS(CDisplayThread),
THREAD_PRIORITY_NORMAL,
0,
CREATE_SUSPENDED);
m_pDisplayThread- >SetOwner(this);
m_pCounterThread = (CCounterThread*)
AfxBeginThread(RUNTIME_CLASS(CCounterThread),
THREAD_PRIORITY_NORMAL,
0,
CREATE_SUSPENDED);
m_pCounterThread- >SetOwner(this);
……
}
然後填加成員函數:

void AddToListBox(LPCTSTR szBuffer);//用於填加列表框顯示

該函數的實現代碼為:

void CMutexesDlg::AddToListBox(LPCTSTR szBuffer)
{
CListBox* pBox = (CListBox*) GetDlgItem(IDC_DATABOX);
ASSERT(pBox != NULL);
if (pBox != NULL){
int x = pBox- >AddString(szBuffer);
pBox- >SetCurSel(x);
if (pBox- >GetCount() > 100)
pBox- >DeleteString(0);
}
}
---- 然後利用ClassWizard 填加用於調整進程優先級、兩個線程優先級以及用於復選線程掛起的函數。

調整進程優先級的代碼為:

void CMutexesDlg::OnSelchangePriorityclass()
{
DWORD dw;
//取焦點選項
CComboBox* pBox = (CComboBox*)
GetDlgItem(IDC_PRIORITYCLASS);
int nCurSel = pBox- >GetCurSel();
switch (nCurSel)
{
case 0:
dw = IDLE_PRIORITY_CLASS;break;
case 1:
default:
dw = NORMAL_PRIORITY_CLASS;break;
case 2:
dw = HIGH_PRIORITY_CLASS;break;
case 3:
dw = REALTIME_PRIORITY_CLASS;break;
}
SetPriorityClass(GetCurrentProcess(), dw);//調整優先級
}

由於調整兩個線程優先級的代碼基本相似,單獨設置一個函數根據不同的ID 來調整線程優先級。該函數代碼為:

void CMutexesDlg::OnPriorityChange(UINT nID)
{
ASSERT(nID == IDC_CNTRTHRDPRIORITY ||
nID == IDC_DSPYTHRDPRIORITY);
DWORD dw;
//取對應該ID的焦點選項
CComboBox* pBox = (CComboBox*) GetDlgItem(nID);
int nCurSel = pBox- >GetCurSel();
switch (nCurSel)
{
case 0:
dw = (DWORD)THREAD_PRIORITY_IDLE;break;
case 1:
dw = (DWORD)THREAD_PRIORITY_LOWEST;break;
case 2:
dw = (DWORD)THREAD_PRIORITY_BELOW_NORMAL;break;
case 3:
default:
dw = (DWORD)THREAD_PRIORITY_NORMAL;break;
case 4:
dw = (DWORD)THREAD_PRIORITY_ABOVE_NORMAL;break;
case 5:
dw = (DWORD)THREAD_PRIORITY_HIGHEST;break;
case 6:
dw = (DWORD)THREAD_PRIORITY_TIME_CRITICAL;break;
}
if (nID == IDC_CNTRTHRDPRIORITY)
m_pCounterThread- >SetThreadPriority(dw);
//調整計數線程優先級
else
m_pDisplayThread- >SetThreadPriority(dw);
//調整顯示線程優先級
}

這樣線程優先級的調整只需要根據不同的ID來調用該函數:

void CMutexesDlg::OnSelchangeDspythrdpriority()
{ OnPriorityChange(IDC_DSPYTHRDPRIORITY);}
void CMutexesDlg::OnSelchangeCntrthrdpriority()
{ OnPriorityChange(IDC_CNTRTHRDPRIORITY);}

復選線程掛起的實現代碼如下:

void CMutexesDlg::OnPause()
{
//取掛起復選框狀態
CButton* pCheck = (CButton*)GetDlgItem(IDC_PAUSE);
BOOL bPaused = ((pCheck- >GetState() & 0x003) != 0);
if (bPaused) {
m_pCounterThread- >SuspendThread();
m_pDisplayThread- >SuspendThread();
}//掛起線程
else {
m_pCounterThread- >ResumeThread();
m_pDisplayThread- >ResumeThread();
}//恢復線程運行
}

程序在::OnClose() 中實現了線程的終止。在本例程當中對線程的終止稍微復雜些。需要注意的是成員變量m_bDone 的作用,在線程的運行當中循環檢測該變量的狀態,最終引起線程的退出。這樣線程的終止是因為函數的退出而自然終止,而非采用強行終止的方法,這樣有利於系統的安全。該程序中使用了PostMessage 函數,該函數發送消息後立即返回,這樣可以避免阻塞。其實現的代碼為:

void CMutexesDlg::OnClose()
{
int nCount = 0;
DWORD dwStatus;
//取掛起復選框狀態
CButton* pCheck = (CButton*) GetDlgItem(IDC_PAUSE);
BOOL bPaused = ((pCheck- >GetState() & 0x003) != 0);
if (bPaused == TRUE){
pCheck- >SetCheck(0);//復選取消
m_pCounterThread- >ResumeThread();
//恢復線程運行
m_pDisplayThread- >ResumeThread();
}
if (m_pCounterThread != NULL){
VERIFY(::GetExitCodeThread(m_pCounterThread- >
m_hThread, &dwStatus));//取計數線程結束碼
if (dwStatus == STILL_ACTIVE){
nCount++;
m_pCounterThread- >m_bDone = TRUE;
}//如果仍為運行狀態,則終止
else{
delete m_pCounterThread;
m_pCounterThread = NULL;
}//如果已經終止,則刪除該線程對象
}
if (m_pDisplayThread != NULL){
VERIFY(::GetExitCodeThread(m_pDisplayThread- >
m_hThread, &dwStatus));//取顯示線程結束碼
if (dwStatus == STILL_ACTIVE){
nCount++;
m_pDisplayThread- >m_bDone = TRUE;
}//如果仍為運行狀態,則終止
else{
delete m_pDisplayThread;
m_pDisplayThread = NULL;
}//如果已經終止,則刪除該線程對象
}
if (nCount == 0)//兩個線程均終止,則關閉程序
CDialog::OnClose();
else //否則發送WM_CLOSE消息
PostMessage(WM_CLOSE, 0, 0);
}

在例程具體實現中用到了許多函數,在這裡不一一贅述,關於函數的具體意義和用法,可以查閱聯機幫助。

  1. 上一頁:
  2. 下一頁:
Copyright © 程式師世界 All Rights Reserved