摘 要:本文簡(jiǎn)單介紹了Windows環(huán)境下進(jìn)行多線(xiàn)程編程的意義,重點(diǎn)討論了C++Builder環(huán)境下開(kāi)發(fā)多線(xiàn)程應用程序這一問(wèn)題,并通過(guò)實(shí)現生產(chǎn)者-消費者問(wèn)題,幫我們更好地理解同步概念及其實(shí)現方法。
線(xiàn)程之可行性
在很多情況下,可能需要為程序創(chuàng )建線(xiàn)程。這里給出其中一些可能性:
?。?)如果創(chuàng )建的是一個(gè)多文檔接口(Multiple Document Interface,MDI)程序,那么為每個(gè)窗口分配一個(gè)線(xiàn)程就顯得十分重要了,例如,對于一個(gè)通過(guò)多個(gè)Modem同時(shí)連接到多個(gè)主機的MDI通信程序而言,如果每個(gè)窗口都有它自己的線(xiàn)程來(lái)和一個(gè)主機通信,那么整個(gè)事情就簡(jiǎn)化很多。
?。?)如果使用的是一臺有多個(gè)處理器的機器,并希望充分利用所有可能獲得的CPU資源,那么就需要將應用程序分解成多個(gè)線(xiàn)程。Windows2000中CPU的劃分單位為線(xiàn)程。因此,如果程序只包含一個(gè)線(xiàn)程,那么,默認環(huán)境下該程序只能使用其中一個(gè)CPU.但是,如果將此程序劃分為多個(gè)線(xiàn)程,那么Windows2000就可以在不同的CPU上運行各個(gè)線(xiàn)程。
?。?)在后臺運行的某些任務(wù)的同時(shí),要求用戶(hù)還可以繼續使用應用程序進(jìn)行工作。利用線(xiàn)程很容易實(shí)現這點(diǎn)。例如:可以將一些冗長(cháng)的重算、頁(yè)面格式化操作、文件的讀寫(xiě)等活動(dòng)都放在單獨的線(xiàn)程中,使其在后臺運行,而不會(huì )對用戶(hù)造成影響。
同步
撰寫(xiě)多線(xiàn)程程序的一個(gè)最具挑戰性的問(wèn)題就是:如何讓一個(gè)線(xiàn)程和另一個(gè)線(xiàn)程合作。這引出了一個(gè)非常重要的問(wèn)題:同步。所謂同步是指進(jìn)程、線(xiàn)程間相互通信時(shí)避免破壞各自數據的能力。Windows環(huán)境下的同步問(wèn)題是由Win32系統的CPU時(shí)間片分配方式引起的。雖然在某一時(shí)刻,只有一個(gè)線(xiàn)程占用CPU(單CPU)時(shí)間,但是無(wú)法知道在什么時(shí)候,在什么地方線(xiàn)程被打斷,這樣如何保證線(xiàn)程之間不破壞彼此的數據就顯得格外重要。同步問(wèn)題是如此重要,也相當有趣,因而吸引了不少學(xué)者對他進(jìn)行研究,由此產(chǎn)成了一系列經(jīng)典的進(jìn)程同步問(wèn)題,其中較有代表性的是"生產(chǎn)者-消費者問(wèn)題"、"讀者-寫(xiě)者問(wèn)題""哲學(xué)家進(jìn)餐問(wèn)題"等。在此,本文簡(jiǎn)要討論了C++Builder平臺下如何利用多線(xiàn)程編程技術(shù)實(shí)現"生產(chǎn)者-消費者"問(wèn)題,幫助我們更好得理解同步概念及其實(shí)現方法。
生產(chǎn)者-消費者問(wèn)題
生產(chǎn)者-消費者問(wèn)題是一個(gè)著(zhù)名的進(jìn)程同步問(wèn)題。它描述的是:有一群生產(chǎn)者進(jìn)程在生產(chǎn)消息,并將此消息提供給消費者進(jìn)程去消費。為使生產(chǎn)者進(jìn)程和消費者進(jìn)程能并發(fā)進(jìn)行,在他們之間設置了一個(gè)具有N個(gè)緩沖區的緩沖池,生產(chǎn)者進(jìn)程可以將它所生產(chǎn)的消息放入一個(gè)緩沖區中,消費者進(jìn)程可以從一個(gè)緩沖區中取得一個(gè)消息消費。盡管所有的生產(chǎn)者進(jìn)程和消費者進(jìn)程都是以異步方式進(jìn)行的,但他們之間必須保持同步,即不允許消費者進(jìn)程到一個(gè)空的緩沖區中去取消息,也不允許生產(chǎn)者進(jìn)程向一個(gè)已裝滿(mǎn)消息且尚未被取走消息的緩沖區中投放消息。
C++Builder多線(xiàn)程應用程序編程基礎
1、使用C++Builder提供的TThread類(lèi)
VCL類(lèi)庫提供了用于線(xiàn)程編程的TThread類(lèi)。在TThread類(lèi)中封裝了Windows中關(guān)于線(xiàn)程機制的WindowSAPI.對于大多數的應用程序來(lái)說(shuō),可在應用程序中使用線(xiàn)程對象來(lái)表示執行線(xiàn)程。線(xiàn)程對象通過(guò)封裝使用線(xiàn)程所需的內容,簡(jiǎn)化了多線(xiàn)程應用程序的編寫(xiě)。注意,線(xiàn)程對象不允許控制線(xiàn)程堆棧的大小或其安全屬性。若需要控制這些,必須使用WindowsAPI的Create Thread()或Begin Thread()函數。
TThread類(lèi)有以下一些屬性和方法:
1) 屬性:
·Priority:優(yōu)先級屬性??梢栽O置線(xiàn)程的優(yōu)先級。
·Return Value:返回值屬性。當線(xiàn)程介紹時(shí)返回給其他線(xiàn)程一個(gè)數值。
·Suspended:掛起屬性??梢耘袛嗑€(xiàn)程是否被掛起。
·Terminated:結束屬性。用來(lái)標志是否應該結束線(xiàn)程。
·ThreadID:標識號屬性。在整個(gè)系統中線(xiàn)程的標識號。使用Windows API函數時(shí)該屬性非常有用。
2) 方法:
·Do Terminate:產(chǎn)生一個(gè)On Terminate事件,但是不結束線(xiàn)程的執行。
·Resume:?jiǎn)拘岩粋€(gè)線(xiàn)程繼續執行。
·Suspend:掛起一個(gè)線(xiàn)程,要與Resume過(guò)程成對使用。
·Synchronize:由主VCL線(xiàn)程調用的一個(gè)同步過(guò)程。
·Terminate:將Terminate屬性設置為T(mén)rue,中止線(xiàn)程的執行。
·Wait For:等待線(xiàn)程的中止并返回Return Value屬性的數值。
2、協(xié)調線(xiàn)程
在編寫(xiě)線(xiàn)程執行時(shí)運行的代碼時(shí),必須考慮到可能同步執行的其他線(xiàn)程的行為。特別注意,避免兩個(gè)線(xiàn)程試圖同時(shí)使用相同的全局對象或變量。另外,一個(gè)線(xiàn)程中的代碼會(huì )依賴(lài)其他線(xiàn)程執行任務(wù)的結果。
1) 避免同時(shí)訪(fǎng)問(wèn)
為避免在訪(fǎng)問(wèn)全局對象或變量時(shí)與其他線(xiàn)程發(fā)生沖突,可能需要暫停其他線(xiàn)程的執行,直到該線(xiàn)程代碼完成操作。
?。?)鎖定對象。一些對象內置了鎖定功能,以防止其他線(xiàn)程使用該對象的實(shí)例。例如,畫(huà)布對象(TCanvas及其派生類(lèi))有一種Lock()函數可以防止其他線(xiàn)程訪(fǎng)問(wèn)畫(huà)布,直到調用Unlock()函數。顯然,這種方法只對部分類(lèi)有效。
?。?)使用重要區段。
若對象沒(méi)有提供內置的鎖定功能,可使用重要區段。重要區段像門(mén)一樣,每次只允許一個(gè)線(xiàn)程進(jìn)入,要使用重要區段,需創(chuàng )建TCriticalSection的全局實(shí)例。TCriticalSection有兩個(gè)函數:Acquire()(阻止其他線(xiàn)程執行該區域)及Release()(取消對其他線(xiàn)程的阻止)。
?。?)使用多重讀、獨占寫(xiě)的同步器。
有時(shí),只需要等待線(xiàn)程完成一些操作而不是等待線(xiàn)程執行結束。為此,可使用一個(gè)事件對象。事件對象(TEvent)應具有全局范圍以便他們能夠為所有線(xiàn)程可見(jiàn)。當一個(gè)線(xiàn)程完成一個(gè)被其他線(xiàn)程依賴(lài)的操作時(shí),調用TEvent::Set Event()函數。Set Event發(fā)出一個(gè)信號,以便其他線(xiàn)程可以檢查并得知操作完成。要關(guān)掉信號,則使用Reset Event()函數。
例如,當必須等待若干線(xiàn)程完成其執行而不是單個(gè)線(xiàn)程時(shí)。因為不知道哪個(gè)線(xiàn)程最后完成,也就不能對某個(gè)線(xiàn)程使用Wait For()函數。此時(shí),可通過(guò)調用Set Event以在線(xiàn)程結束時(shí)累加計數值并在最后一個(gè)線(xiàn)程結束時(shí)發(fā)出信號以指示所有線(xiàn)程結束。
多線(xiàn)程應用程序編程實(shí)例
下面是一個(gè)實(shí)現"生產(chǎn)者-消費者問(wèn)題"的多線(xiàn)程應用實(shí)例。在此例中,我們按上面介紹的方法構造了兩個(gè)TThread的子類(lèi)TProducerThread(生產(chǎn)者線(xiàn)程)和TCustomerThread(消費者線(xiàn)程),生產(chǎn)和消費的商品僅僅是一個(gè)整數。在協(xié)調生產(chǎn)和消費的過(guò)程中,重要區段(TCriticalSection)和事件(TEvent)得到了應用。生產(chǎn)者通過(guò)TEvent類(lèi)的對象Begin Consume來(lái)通知消費者開(kāi)始消費,而消費者通過(guò)TEent類(lèi)的對象Begin Produce通知生產(chǎn)者開(kāi)始生產(chǎn)。程序中共有兩個(gè)生產(chǎn)者,一個(gè)消費者。在兩個(gè)生產(chǎn)者之間,通過(guò)TCriticalSection類(lèi)的對象同步。其運行界面如圖1所示。

主要源程序如下所示:
生產(chǎn)者線(xiàn)程:
| Void __fast call TProducerThread:: Execute () { //---- Place thread code here ---- Int i = 0; Int j; while(i<100) //每個(gè)生產(chǎn)者線(xiàn)程生產(chǎn)100個(gè)商品 { Sleep(1000);//延遲,為清楚得顯示執行效果 if(Form1->buffer_size > 0)//緩沖池不空,通知消費者消費 { Form1->Begin Consumer->Set Event (); } Form1->Produce Guard->Acquire (); i++; StrResult = IntToStr (i); J = Form1->buffer_size; Form1->Product [j] = i; Form1->buffer_size++; Synchronize(Show Result);//刷新界面,顯示最新生產(chǎn)-消費狀況 Form1->Begin Consumer->Set Event();//通知消費者消費 if(Form1->buffer_size == 5)//緩沖池滿(mǎn),掛起生產(chǎn)者線(xiàn)程,直到通知再生產(chǎn) { Form1->Begin Produce->Wait For (INFINITE); } Sleep (1000); Form1->Produce Guard->Release (); } While (Form1->buffer_size > 0) { Form1->Begin Consumer->Set Event (); } } |
| Void __fast call TConsumerThread::Execute() { //---- Place thread code here ---- Int j; For (int i = 0;i < 200;i++) { Sleep(100); //延遲,為清楚得顯示執行效果 Form1->Begin Consumer->Wait For(INFINITE);//掛起消費者線(xiàn)程,直到通知再消費 J = Form1->buffer_size - 1; StrResult = IntToStr (Form1->Product [j]); Form1->buffer_size--; Synchronize(Show Result); //刷新界面,顯示最新生產(chǎn)-消費狀況 if(Form1->buffer_size == 4)//緩沖池不再full,喚醒由于緩沖池full而掛起的生產(chǎn)者線(xiàn)程 { Form1->Begin Produce->Set Event (); } Sleep (100); } } |
聯(lián)系客服