?? sy0603.htm
字號(hào):
<html>
<HEAD>
<TITLE>Visual C++與計(jì)算機(jī)接口</TITLE>
<META HTTP-EQUIV="Content-Type" CONTENT="text/html;charset=gb2312" >
</HEAD>
<body>
<font color="#0000FF">線程間的通信</font>
<p>
(一)實(shí)驗(yàn)?zāi)康模?lt;BR>
學(xué)習(xí)線程間的通信<BR><BR>
(二)線程間的通信:<BR><BR>
在一個(gè)多線程的應(yīng)用程序中,所有線程共享進(jìn)程資源,協(xié)同工作。所以,線程之間的通信是編寫多線程應(yīng)用的必不可少的環(huán)節(jié)。<BR>
線程之間的通信包括互斥、同步等,它是多線程設(shè)計(jì)中最難控制的部分,也是關(guān)鍵部分。<BR><BR>
1、線程間的互斥<BR><BR>
(1) 臨界區(qū)<BR>
在一個(gè)多線程的應(yīng)用程序中,可能存在這樣的危險(xiǎn):一個(gè)線程以某種其他線程不可預(yù)料的方式修改資源。<BR>
例如兩個(gè)線程都對(duì)同一個(gè)文件進(jìn)行讀寫,兩個(gè)線程都在進(jìn)行繪圖,一個(gè)線程正在使用一段內(nèi)存而另一個(gè)線程卻正在修改這段內(nèi)存的值等,都可能出現(xiàn)不可預(yù)料的結(jié)果。<BR>
可以把不允許多個(gè)線程交叉運(yùn)行的一段程序稱為臨界區(qū)。它是由于多個(gè)線程共享公用數(shù)據(jù)或公用變量而引起的。<BR>
臨界區(qū)也被稱為:訪問公用數(shù)據(jù)的那段程序。<BR><BR>
(2) 互斥<BR>
為使多個(gè)線程在進(jìn)入自己的臨界區(qū)時(shí)不出現(xiàn)問題,需要實(shí)現(xiàn)線程的互斥運(yùn)行。它應(yīng)滿足:<BR>
·各線程平等,都可隨時(shí)進(jìn)入臨界區(qū)。<BR>
·一個(gè)不在臨界區(qū)執(zhí)行的線程,不可以阻止其他線程進(jìn)入臨界區(qū)。<BR>
·當(dāng)一個(gè)線程正在臨界區(qū)內(nèi)執(zhí)行時(shí),必須阻止其他線程進(jìn)入臨界區(qū)。<BR>
·多個(gè)線程申請(qǐng)進(jìn)入臨界區(qū)時(shí),只能允許一個(gè)線程進(jìn)入。<BR>
·一個(gè)申請(qǐng)進(jìn)入臨界區(qū)的線程,應(yīng)該能在有限時(shí)間內(nèi)進(jìn)入,不會(huì)發(fā)生死鎖。<BR><BR>
(3) 臨界區(qū)類<BR>
MFC定義了臨界區(qū)封裝類CCriticalSection,可以比較方便的實(shí)現(xiàn)線程間的互斥。<BR>
首先定義一個(gè)CCriticalSection類對(duì)象,該對(duì)象通常應(yīng)被定義為全局變量,以便跨線程使用。<BR>
當(dāng)線程要進(jìn)入臨界區(qū)時(shí),調(diào)用其成員函數(shù)Lock()。若臨界區(qū)空閑,該函數(shù)將鎖定臨界區(qū);若臨界區(qū)已經(jīng)被鎖定,該函數(shù)將被阻塞(不耗費(fèi)機(jī)時(shí)),直至臨界區(qū)解鎖。<BR>
當(dāng)線程要退出臨界區(qū)時(shí),調(diào)用其成員函數(shù)Unlock()解鎖,以便其他線程可以進(jìn)入臨界區(qū)。<BR><BR>
2、線程間的同步<BR><BR>
(1) 線程間的直接制約<BR>
有時(shí)一個(gè)線程的執(zhí)行條件是另一個(gè)線程的執(zhí)行結(jié)果,所以只有等待另一個(gè)線程完成某項(xiàng)操作后,該線程才可繼續(xù)執(zhí)行。例如計(jì)算線程和打印線程。<BR>
這種由于線程間的功能邏輯關(guān)系引起的,稱為線程間的直接制約。而由于共享資源引起的線程執(zhí)行速度的制約,稱為間接制約。<BR>
存在直接制約關(guān)系的一個(gè)線程可以在另一個(gè)線程的執(zhí)行條件滿足后,給對(duì)方發(fā)送相應(yīng)消息或信號(hào)。這樣,被制約的線程可以在條件不滿足時(shí)處于阻塞狀態(tài),條件滿足后,被對(duì)方喚醒繼續(xù)工作。<BR>
這就是線程間的同步方式。<BR><BR>
(2) 等待函數(shù)<BR>
被制約的線程等待條件的滿足時(shí),可以使用Win32 API的信號(hào)等待函數(shù)。<BR>
若等到信號(hào)或在時(shí)限內(nèi)未等到信號(hào),等待函數(shù)都將返回,否則線程阻塞。<BR>
函數(shù)WaitForSingleObject()等待一個(gè)對(duì)象的信號(hào)狀態(tài)。<BR>
DWORD WaitForSingleObject(HANDLE hHandle, DWORD dwMilliseconds);<BR>
其中:hHandle可以是事件對(duì)象、互斥對(duì)象或信號(hào)量等。<BR>
dwMilliseconds為等待時(shí)限,可以為0,也可以為INFINITE表示無限期等待。<BR>
函數(shù)返回值為WAIT_OBJECT_0表明有信號(hào)或WAIT_TIMEOUT表明超時(shí)。<BR>
函數(shù)WaitForMultipleObjects()可以同時(shí)等待多各對(duì)象的信號(hào)狀態(tài)。<BR>
DWORD WaitForMultipleObjects(DWORD nCount, CONST HANDLE *lpHandles, BOOL fWaitAll, DWORD dwMilliseconds);<BR>
其中:nCount為等待的對(duì)象的個(gè)數(shù)。<BR>
lpHandles為存放等待的對(duì)象的數(shù)組。<BR>
fWaitAll等于TRUE指明所有對(duì)象均有信號(hào)時(shí)返回。<BR>
fWaitAll等于FALSE指明其中之一有信號(hào)時(shí)返回。<BR><BR>
(3) 事件對(duì)象<BR>
MFC定義了CEvent類封裝了系統(tǒng)的事件對(duì)象。它可以延遲一個(gè)線程的運(yùn)行以等待另一個(gè)線程。<BR>
事件對(duì)象可以是有信號(hào)狀態(tài)或無信號(hào)狀態(tài),可以被等待函數(shù)調(diào)用。函數(shù)SetEvent()和ResetEvent()用于將事件置為有信號(hào)狀態(tài)或無信號(hào)狀態(tài)。<BR>
事件對(duì)象有兩種復(fù)位方式:<BR>
·手工復(fù)位:當(dāng)有信號(hào)時(shí),無論釋放了多少等待的線程,只有在ResetEvent()后才置為無信號(hào)。<BR>
·自動(dòng)復(fù)位:當(dāng)有信號(hào)時(shí),只要釋放了一個(gè)等待的線程,就自動(dòng)轉(zhuǎn)為無信號(hào)。其他等待線程或同一線程的下一次等待不被釋放。<BR>
事件對(duì)象的初始化:<BR>
CEvent(BOOL bInitiallyOwn = FALSE, BOOL bManualReset = FALSE);<BR>
其中:bInitiallyOwn =FALSE初始化為無信號(hào)狀態(tài),=TRUE初始化為有信號(hào)狀態(tài)。<BR>
bManualReset =FALSE為手工復(fù)位,=TRUE為自動(dòng)復(fù)位。<BR><BR>
(4) 互斥對(duì)象<BR>
MFC定義了CMutex類封裝了系統(tǒng)的互斥對(duì)象。它可以保護(hù)共享資源防止其為多個(gè)線程同時(shí)訪問。<BR>
互斥對(duì)象在未被任何線程擁用時(shí)為有信號(hào)狀態(tài),當(dāng)被任一個(gè)線程擁有時(shí)為無信號(hào)。<BR>
當(dāng)關(guān)于互斥對(duì)象的等待函數(shù)返回成功時(shí),互斥對(duì)象變?yōu)闊o信號(hào)。當(dāng)進(jìn)程結(jié)束后,則需調(diào)用Unlock()函數(shù)以使互斥對(duì)象變?yōu)橛行盘?hào)。<BR>
互斥對(duì)象的初始化:<BR>
CMutex(BOOL bInitiallyOwn = FALSE);<BR>
其中:bInitiallyOwn 為互斥對(duì)象的初始狀態(tài)。<BR><BR>
(5) 信號(hào)量對(duì)象<BR>
MFC定義了CSemaphore類封裝了系統(tǒng)的信號(hào)量對(duì)象。它是維護(hù)一個(gè)從0到指定的最大值的計(jì)數(shù)的同步對(duì)象。<BR>
當(dāng)信號(hào)量對(duì)象創(chuàng)建時(shí),擁有一個(gè)初始計(jì)數(shù),當(dāng)線程使用和釋放信號(hào)量時(shí),計(jì)數(shù)增減。只要計(jì)數(shù)大于0,信號(hào)量都處于有信號(hào)狀態(tài),若計(jì)數(shù)為0,信號(hào)量處于無信號(hào)狀態(tài)。<BR>
信號(hào)量主要用于限制線程的最大數(shù)目。當(dāng)關(guān)于信號(hào)量的等待函數(shù)返回成功時(shí),信號(hào)量的計(jì)數(shù)自動(dòng)減一。當(dāng)進(jìn)程結(jié)束后,則需調(diào)用Unlock()函數(shù)以使計(jì)數(shù)加一。<BR>
信號(hào)量對(duì)象的初始化:<BR>
CSemaphore(LONG lInitialCount = 1, LONG lMaxCount = 1);<BR>
其中:lInitialCount 為信號(hào)量的初始計(jì)數(shù)。<BR>
lMaxCount 為信號(hào)量的最大計(jì)數(shù)。<BR><BR>
(三)實(shí)驗(yàn)步驟:<BR><BR>
1、建立一個(gè)MFC應(yīng)用程序。<BR><BR>
2、打開stdafx.h文件,加入:<BR>
#include < afxtempl.h ><BR>
#include < afxmt.h ><BR><BR>
3、在View類的頭文件中添加:<BR>
#define BEZIER_POINTS 6 //曲線頂點(diǎn)數(shù)<BR>
UINT DrawThread(LPVOID pView);<BR><BR>
4、在View類中添加成員變量:<BR>
HDC m_hDC; //為輔線程準(zhǔn)備的繪圖設(shè)備句柄<BR>
int m_nLineNumber; //曲線總數(shù),即線程總數(shù)<BR>
CTypedPtrList < CObList,CWinThread* > m_listThread; //保存所有輔線程對(duì)象<BR><BR>
5、在View類的構(gòu)造函數(shù)中加入代碼:<BR>
m_hDC=NULL;<BR>
m_nLineNumber=0;<BR><BR>
6、添加菜單StartThread并在View類映射函數(shù)OnStartThread,加入代碼:<BR>
if(m_hDC==NULL) m_hDC=::GetDC(m_hWnd); //該設(shè)備句柄將在所有線程中共享<BR>
CWinThread* pp=AfxBeginThread(DrawThread,this);<BR>
m_listThread.AddTail(pp); //將線程對(duì)象保存在集合中<BR><BR>
7、添加菜單KillThread并在View類映射函數(shù)OnKillThread,加入代碼:<BR>
g_eventCloseThreads.SetEvent();<BR>
while (!m_listThread.IsEmpty())<BR>
{<BR>
CWinThread* pThread = m_listThread.RemoveHead();<BR>
WaitForSingleObject(pThread->m_hThread, INFINITE); //等待所有線程關(guān)閉<BR>
}<BR>
g_eventCloseThreads.ResetEvent();<BR>
m_nLineNumber=0;<BR>
Invalidate();<BR><BR>
8、在View類中加入WM_DESTROY消息映射函數(shù)OnDestroy(),并加入代碼:<BR>
OnKillThreads();<BR>
if(m_hDC!=NULL)<BR>
{<BR>
::ReleaseDC(m_hWnd,m_hDC);<BR>
m_hDC=NULL;<BR>
}<BR>
CView::OnDestroy();<BR>
9、在View類的cpp文件中手工加入全局變量:<BR>
CEvent g_eventCloseThreads(0,1);<BR>
CCriticalSection g_criDrawing;<BR><BR>
10、在View類的cpp文件中加入線程函數(shù):<BR>
UINT DrawThread(LPVOID pParam)<BR>
{<BR>
CMyView *pView=(CMyView*)pParam;<BR>
srand(pView->m_nLineNumber*100);<BR>
<BR>
RECT rcCanvas;<BR>
HPEN oldPen;<BR>
HPEN Pen=::CreatePen(PS_SOLID,1,RGB(rand()%256,rand()%256,rand()%256)); //以隨機(jī)顏色創(chuàng)建畫筆<BR>
<BR>
int m_nMoveDirect=pView->m_nLineNumber++%2;<BR>
POINT m_pointsForLine[BEZIER_POINTS];<BR>
for(int i=0;i< BEZIER_POINTS;i++) //隨機(jī)生成曲線頂點(diǎn)<BR>
{<BR>
m_pointsForLine[i].x=rand()%1000;<BR>
m_pointsForLine[i].y=rand()%1000;<BR>
}<BR>
while(WaitForSingleObject(g_eventCloseThreads,0) == WAIT_TIMEOUT)//繪圖中監(jiān)視關(guān)閉事件<BR>
{<BR>
pView->GetClientRect(&rcCanvas);<BR>
for(int i=0;i< BEZIER_POINTS;i++) //移動(dòng)曲線(相對(duì)上次位置)<BR>
{<BR>
if(m_nMoveDirect)<BR>
{<BR>
if((m_pointsForLine[i].y+=2)>abs(rcCanvas.bottom-rcCanvas.top))<BR>
m_pointsForLine[i].y=rcCanvas.top;<BR>
if((m_pointsForLine[i].x+=2)>abs(rcCanvas.right-rcCanvas.left))<BR>
m_pointsForLine[i].x=rcCanvas.left;<BR>
} <BR>
else<BR>
{<BR>
if((m_pointsForLine[i].y-=2) < rcCanvas.top)<BR>
m_pointsForLine[i].y=rcCanvas.bottom;<BR>
if((m_pointsForLine[i].x-=2) < rcCanvas.left)<BR>
m_pointsForLine[i].x=rcCanvas.right;<BR>
}<BR>
}<BR>
<BR>
g_criDrawing.Lock();<BR>
///////////////////// 繪制臨界區(qū)(因?yàn)楣灿猛辉O(shè)備句柄)<BR>
oldPen=(HPEN)::SelectObject(pView->m_hDC,Pen);<BR>
::PolyBezierTo(pView->m_hDC,m_pointsForLine,6);<BR>
::SelectObject(pView->m_hDC,oldPen);<BR>
::GdiFlush();<BR>
////////////////////////<BR>
g_criDrawing.Unlock();<BR>
Sleep(10);<BR>
}<BR>
AfxEndThread(0);<BR>
return 0;<BR>
}<BR>
11、編譯運(yùn)行,查看結(jié)果。<BR><BR>
說明:<BR>
·在菜單StartThread響應(yīng)函數(shù)中開啟線程,可開多個(gè)。<BR>
·在菜單KillThread響應(yīng)函數(shù)中關(guān)閉所有線程。<BR>
·在線程中利用CCriticalSection對(duì)象管理臨界區(qū)<BR>
·利用CEvent對(duì)象實(shí)現(xiàn)線程的安全關(guān)閉<BR><BR>
<a href="l0603.exe">執(zhí)行文件示例</a>
<p></p>
</body>
</html>
?? 快捷鍵說明
復(fù)制代碼
Ctrl + C
搜索代碼
Ctrl + F
全屏模式
F11
切換主題
Ctrl + Shift + D
顯示快捷鍵
?
增大字號(hào)
Ctrl + =
減小字號(hào)
Ctrl + -