C++多執行緒程式設計經驗總結
1. 軟體片段的架構是一套控制軟體操作的規則、模式、程序、執行協議和斷言。
多執行緒架構:一種將工作模式分解為兩個或更多併發執行現場的軟體架構。
分解軟體:分割為單獨邏輯任務的過程,供軟體的工作模式來執行。由於軟體的資料組織和執行流程依賴於對應的併發或並行化模型的可用性。
2.演算法基本執行單元:一條語句(或子語句)
程序執行基本單元:執行緒(thread)
最大執行單元:對話(session)
多執行緒:一個程序內有兩個或更多執行緒併發執行
並行演算法:一個執行緒內包含一條或多條併發執行的指令,這些指令共同形成一個演算法,多執行緒架構被分解成執行緒,並行演算法被分解成單挑指令。當討論並行演算法時,所討論單元指單條指令或子指令;當討論多執行緒時,所討論單元指包含一套指令的邏輯任務,以過程或函式的形式出現。並行演算法面對併發的微觀方面,多執行緒面對併發的巨集觀方面。
3.多執行緒常見架構三種:
(1)客戶機/伺服器:DDE動態資料交換。單個DDE代理可能既是客戶機也是伺服器,訊息系統完成。
網路環境中常見客戶機/伺服器模型應用:檔案伺服器、資料庫伺服器、事務伺服器、應用伺服器、邏輯伺服器
(2)事件驅動程式設計(GUI)
(3)黑板(blackboard)
4.類層次:一些通過繼承相關聯的類的集合。表示一個人物、地點、事件或思想的邏輯家族,也可以包含其他類作為資料成員。
常見類型別:抽象基類、具體類、節點類、介面類、容器和集合類、應用框架類、域類。
5.抽象基類:為後代提供藍圖,不能宣告例項,必須首先從基類派生一個新類,併為抽象類中宣告的所有純虛擬函式提供實際的定義。抽象類必須至少有一個有用的後代。(與具體類形成鮮明對比,具體類不支援派生後代),對於抽象類,必須至少有一個純虛擬函式。virtual return type function()=0;
雖然純抽象虛擬函式可用於強制實施介面策略,但它並不是百分之百安全,不能阻止派生類的設計者定義成員函式完成該成員函式之外的其他事情,可以講他們實現為啞元函式(即什麼也不做),即純抽象虛擬函式不能強迫派生類實現執行特定任務的函式。
6.具體類:思想終結者(用於包含安全類和多執行緒類的類層次的終點)
具體類可以構建於其他類,但具體類作為進一步繼承的終點。基類具有一般性,但具體類具有特殊性,不會繼續特殊化。多執行緒類層次中為使用者提供一個具體類,就不必擔心使用者在具體類中對成員函式意義的改變。具體類沒有虛擬函式,不打算用來繼承,而是直接使用。
7.節點類:最強大類型別,提供繼承和多型基礎。設計用於被繼承,但不包含純虛擬函式。非終點類,可以立即使用但同時也著眼於未來,即設計具有可重用性,提供在派生類中覆蓋的虛成員函式。節點類即可以是一個基類,也可以是一個派生類。節點類使用了基類的指標、引用、虛擬函式和執行時多型。當程式設計師修改這個層次時,應完整理解執行時多型、深層複製建構函式、異常處理和簽名解析度。在多執行緒環境中,節點類會增添混亂,使用務必小心。
8.容器和集合類:常規C++類的一個特別應用。
集合和容器是一般性目的的分組結構。常用容器類:陣列和向量類Array/Vector、列表類List、雙端佇列類Deque、佇列和優先權佇列類Queue/Priority Queue
用於操縱異類或同類物件。用於管理一組物件,其方式與傳統陣列管理傳統資料型別(如整型或字元)的方式一樣。使用C++類和結構(struct)這一結構來定義集合和容器。C++標準庫包括STL標準模板庫,它是用作具體類的容器類的一個庫。
9.在多執行緒程式中,集合和容器類與執行緒互動的主要途徑:執行緒間通訊ITC、多執行緒伺服器
程序間通訊方式:使用管道、命名管道、動態資料交換、命令列引數、共享記憶體或檔案。這些結構作為多個執行程序間的外部連結,讓這些程序相互通訊與傳遞資料或命令,通過呼叫作業系統API來管理(如建立、同步化、阻塞、取消阻塞、緩衝、關閉和銷燬結構關聯記憶體開銷)。
執行緒間通訊方式:通過一個全域性變數、全域性陣列、全域性結構,或通過傳統引數傳遞來通訊。在面向物件程式中,兩個執行緒可以通過全域性容器和集合類來通訊,形式如下:
面向物件列表、面向物件向量、面向物件集、面向物件多集、面向物件圖示、面向物件雙端佇列、面向物件佇列、面向物件對映、面向物件多重對映。
【注意】面向物件元件進行執行緒間通訊具有巨大優點。
10.實現搜尋引擎的一種最有效技術:將搜尋分成一系列併發執行的執行緒,每個執行緒可以訪問所需要的搜尋元件。執行緒可以將集合和容器物件用作執行緒間通訊的渠道。
多執行緒搜尋引擎元件:集合mt_set和容器物件lqueue元件、執行緒物件ct_thread。
將多執行緒搜尋引擎分成若干不同的任務,一個任務可以讀取網際網路上所有檔案的索引,另一個任務可以為預備匹配預搜尋這些檔案的索引,以及其他多個任務進行詳細的預備性匹配。
指派執行緒A來讀取主索引,當執行緒A讀取主索引時,可以指派執行緒B開始從預備匹配的主索引過濾每個索引。儲存預備匹配的檔名時,指派執行緒C來檢查我們所需要的項,指派執行緒D來檢查檔案中我們不想要的項;一旦全面啟動,執行緒將併發執行,這些執行緒可以共享集合(set)和容器物件(queue)進行執行緒間通訊。
定義4個集set:(1)當前被分析檔案的單詞集合 (2)我們感興趣主題的單詞集合 (3)我們不感興趣的單詞集合 (4)我們感興趣的單詞集合的相關項。
當執行緒A從主索引中讀取檔名時,它將這些檔名及其位置放進面向物件佇列中。執行緒B刪除並處理這些檔名來訪問他們的單個索引。執行緒B查詢預備匹配時,它將匹配放入另一個面向物件佇列中,同時執行緒B還將當前位於佇列前端的檔案放進集A。一旦集A中存在一個檔案,執行緒D和執行緒C使用基本集運算(交集、並集、差集)來進行相應的檔案分析。
(1)執行緒安全集合(mt_set)——更容易在多執行緒環境中使用:
template <class T>class_mt_set:virtual private mutex{
private:
set<T,less<T>>S;
public:
mt_set(void);
mt_set(set<T,less<T>>X);
set<T,less<T>> setUnion(set<T,less<T>>X);
set<T,less<T>> intersection(set<T,less<T>>X);
int membership(set<T,less<T>>X);
set<T,less<T>> difference( set<T,less<T>>X);
};
(2)執行緒安全容器佇列(lqueue):
template <class T> classlqueue:virtual private named_mutex,virtual private event_mutex{
protected:
deque SafeQueue;
public:
lqueue(char *MName,int Own,char *EName,int Initial,unsigned long Dur);
inline void insert(T X);
inline T remove(void);
inline T front(void);
inline T back(void);
inline unsigned int empty(void);
inline void erase(void);
void wait(void);
void broadCast(void);
};
[注意]在多執行緒環境中,使用外部迭代器具有固定的難度,因此去掉了mt_set類和lqueue類的迭代器功能。
兩個類都是修改STL容器類的介面類,都私有繼承了用於保護類內部臨界區的互斥量。
(3)執行緒物件(ct_thread)(POSIX Pthread API)——隱藏了對應作業系統環境的實現細節,用於設計在不同作業系統環境中可移植的程式。
class ct_thread{
private:
pthread_t ThreadId;
pthread_attr_t *Attr;
public:
ct_thread(void);
ct_thread(pthread_attr_t *Attribute);
~ct_thread(void);
void begin(FunctionPtr PFN,void *X);
void wait(void);
pthread_t threadHandle(void);
pthread_t threadId(void);
};
11.作為多執行緒伺服器的容器類:
當容器或集合類用作執行緒間通訊的工具,執行緒位於類外部;當容器或集合類用作一個伺服器時,類本身將包含多個執行緒。多執行緒集伺服器[set_server類]:用於將類分解成多個執行緒的簡單技術。
set_server類用於提供多執行緒服務,多執行緒服務由mt_set元件來實現。即set_server類的使用者請求一個服務,set_server類然後建立一個執行緒來執行這個服務,該執行緒將請求傳遞給mt_set類,mt_set類完成這個請求,並返回結果。
12.多執行緒類簡單架構基石:6個基本元件
宿主類、執行緒類、互斥和事件類、友元成員函式、域類、強制轉換基本元素
13.宿主類:使用者與之互動的類(多執行緒應用程式框架、多執行緒伺服器)
宿主類被分解成兩個或更多的執行緒,每個執行緒執行宿主類一個友元函式,當執行友元函式時,宿主類將this指標作為一個引數傳遞給該執行緒。this指標在友元函式中被強制轉換成指向適當型別的類。this指標形成了宿主類與位於另一個執行緒中的域類間的執行緒間通訊。每個友元函式將建立一個或多個域物件(domain object)來完成執行緒請求。
template<class T> struct server_argument{ //用於儲存多個執行緒間以及主機與客戶間的輸入或輸出引數
set<T,less<T>> A; //需要在友元函式中構建的域類資訊
set<T,less<T>> B; //引數、訊息(來自客戶的請求)
set<T,less<T>> Result; //包含物件集B中資訊操作或處理結果
}
template<class T> classset_server:virtual private mutex{ //伺服器類中每個成員函式表示一個客戶的請求,每個成員函式建立一個執行某友元函式的執行緒。
protected:
server_argument<T> Argument;
public:
set_server(void);
//用於建立客戶請求執行緒的友元函式指標
friend void *intersection(void *X);
friend void *setUnion(void *X);
friend void *difference(void *X);
friend void *membership(void *X);
set<T,less<T>> intersect(set<T,less<T>>X,set<T,less<T>> Y);
set<T,less<T>> setUnion(set<T,less<T>>X,set<T,less<T>> Y);
set<T,less<T>> difference(set<T,less<T>>X,set<T,less<T>> Y);
int membership(set<T,less<T>>X,set<T,less<T>>Y);};
//並集執行緒實現程式碼:
template<class T>set<T,less<T>>
set_server<T>::setUnion(set<T,less<T>>X,set<T,less<T>>Y)
{ lock();
ct_thread Thread;
Argument.A=X;
Argument.B=Y;
Thread.begin(::setUnion,this); //友元函式建立域物件與伺服器物件進行通訊,使用setUnion友元函式指標(即執行緒入口點)建立執行緒並執行setUnion()友元函式。
Thread.wait();
unlock();
return(Argument.Result);
}
setUnion()友元函式宣告如下:
void *setUnion(void *X)
{ set_server<String> *Server;
Server=static_cast<set_server<String> *>(X);
mt_set<String> SafeSet(Server->Argument.A);
Server->Argument.Result=SafeSet.setUnion(Server->Argument.B); //儲存set_server物件的Argument資料成員中並集的結果
}
14.void* 充當執行緒所建立函式的一種命令列引數。
void*引數幾乎可以給執行緒的主函式傳遞任何內容。該引數可以強制轉換成任何一種型別。對於多執行緒物件,void*將被轉換成this指標指向的任何型別。
set_server類是一個執行緒,setUnion()成員函式位於另一個執行緒,而this指標是兩個執行緒間的通訊點。通過型別轉換static_cast建立了兩個執行緒間的通訊連結後,setUnion()友元函式建立域物件mt_set類。
15.多執行緒伺服器簡單程式: 使用多執行緒集伺服器來執行集A和集B之間的並集,當執行兩個集之間的並集時,兩個集中的成員被聯合,並變成第三個集的成員。
#include "setserver.cpp"
#include<set.h>
#include<iostream.h>
set<String,less<String>> A;
set<String,less<String>> B;
set<String,less<String>> C;
set<String,less<String>>::iteratorI;
void main(void)
{ cout<<"Start mainthread"<<endl;
A.insert("hello");
B.insert("I am alive");
set_server<String> Server;
C=Server.setUnion(A,B);
I=C.begin();
cout<<"from main thread"<<endl;
while(I!=C.end())
{cout<<*I<<endl;
I++;
}
}
16.應用框架類:屬於最容易理解但最難以構建的類層次,為思想建模(即為物件互動、工作模式、動作序列、軟體事務和程序內容來建模)。當設計面向物件應用框架時,物件是相關聯動作序列。應用框架用作整個應用的模式,它具體化應用具有的基礎結構或骨架而沒有提供應用的細節。
程式是特定問題的一般性解決方案,應用框架是某問題類別的特定解決方案。
設計類庫(提供面向物件訪問磁碟I/O、數學函式、字串操縱、記憶體管理、影象處理等等)與設計應用框架相比,前者基於過程庫更容易實現。
過程庫進行構建無法適應於改變的環境、軟體的大型化,而類庫改進了這種狀況。通過新增面向物件特徵,可以在過程庫的基礎上改進得到類庫。
應用框架設計需要對軟體分解有一個全面的理解。分解為提供者實現和客戶實現部分。在多執行緒應用框架中,框架執行的工作模式必須分解為一套執行緒一般性部分和特定應用部分。
【注意】C++通過抽象基類、虛基類、虛擬函式、類複合、類聚集、友元函式、模板和新型別轉換運算子來支援軟體分解。
17.應用框架類層次:
應用框架是框架提供者與框架使用者間的合作軟體契約(如交通工具框架提供者元件:引擎、底盤長度、座位數量和點火系統;而交通工具使用者元件:引擎、底盤長度、座位數量、點火系統、交通車體)。框架設計者提供框架的部分功能(理想情況下提供大部分功能),同時,該框架的使用者完善框架的功能。
框架提供者應標識和提供應用必須執行的基本工作模式,框架使用者要提供所有抽象虛擬函式和建構函式引數的定義。
18.框架分解:2個元件(框架設計者提供的一般性方式捕獲的特定動作序列以及物件間關係的框架部分、框架使用者提供的ensemble域知識專家知識規則和特定解決方案的策略),簡單來說即框架應用形式和應用具體內容,框架則提供應用的主要控制流程。
框架指定動作序列由三種成員函式來實現:a.虛成員函式(使用者可覆蓋替換,選擇權取決於使用者) b.純虛成員函式(使用者提供自己的定義,必須提供的實際函式否則程式不能編譯,純虛擬函式都必須在派生類中定義)c.常規成員函式(實現普通動作序列,派生類不需要更改或提供,在框架中提供的常規函式越多越好)
19.提供ensemble:
應用框架內容可通過繼承、複合/結合來提供。一般來說,程式設計師在使用框架之前,必須繼承框架類、為所有純虛擬函式提供定義,覆蓋必須的虛擬函式 來提供框架的內容。
當通過建構函式、物件的指標和引用提供ensemble時,即是通過複合來構建框架內容。
20.集合和容器類 與 應用框架類的區別:
集合和容器類:為各種各樣方式使用的一般目的的結構。
應用框架:只提供廣泛問題類別的一種解決方案。(如:查詢處理器框架:獲取字串、驗證字串、解析字串和判斷字串動作序列),框架不能做其他任何事。