C++併發程式設計框架Theron(5)——File reader(1)
1 前言
在上一篇博文,我主要通過Hello world!的示例,介紹了actors,frameworks,messages和receivers幾個構建Theron框架程式的要點。但是Hello world!例項只是一個再簡單不過的單actor的應用程式,我們學習Theron框架自然是希望多個actor相互協作來達到多執行緒開發的目的。在本篇博文中,我們會學習到一個更復雜,更有實際用處的程式示例。
2 File reader
File reader示例是建立一個檔案伺服器,用來為客戶端讀取來自磁碟檔案中的資料。我們要實現的是這樣一個訊息傳遞的互動:傳送給actor一條訊息,告訴它去讀取一個檔案資料到快取區,並且隨後再返回一條讀取完畢的訊息。
我們可以看到,使用Theron建立這樣一個檔案伺服器可以帶來下面幾點優勢:
①、因為檔案閱讀被表達成actors,它可以與系統剩下的程式同時執行,沒有額外的花費。因為使用訊息傳遞機制。所以檔案讀取時非同步而非堵塞的;
②、將檔案閱讀器放置到一個framework中,我們可以建立一個執行緒池(多個執行緒)阻塞檔案閱讀,從而不會餓死系統剩下程式。
③、支援更多的並行檔案閱讀只是意味著增加了更多的相同actor的拷貝,程式實現很簡單,但是可以多執行緒完成很多檔案的讀取。
2.1 actor
首先,我們建立一個通用的多執行緒系統形式“Worker”actor,如下:
#include <Theron/Theron.h>
template <class WorkMessage>
class Worker : public Theron::Actor
{
public:
// 建構函式
Worker(Theron::Framework &framework) : Theron::Actor(framework)
{
RegisterHandler(this, &Worker::Handler);
}
private:
// 訊息處理函式
void Handler(const WorkMessage &message, const Theron::Address from)
{
// 訊息引數是const,搜易我們需要拷貝它來改變它的性質
WorkMessage result(message);
result.Process();
// 返回執行緒訊息給傳送者
Send(result, from);
}
};
通過它自己,這個actor模板不能做太多事情。當例項化一個具體的WorkMessage型別,它會變成該訊息型別的一個處理者(也就是一個執行緒)。Worker從一個叫作Handler()的訊息處理函式中獲得處理WorkMessage訊息的能力。這個處理函式通過拷貝訊息來響應WorkMessage訊息,並且在其上執行Process()方法,最後將其返回。重要的一點是,我們將Process()放在一個actor裡面被呼叫,從而使得它能與外面程式一起執行。
這塊模板類的作用是,使得Worker能夠處理許多不一樣的訊息型別,即重複使用。
2.2 訊息類定義
現在讓我們寫一個ReadRequest訊息型別來表示檔案讀取的請求,ReadRequest物件可以呼叫一個Process()方法,所以可以作為WorkMesssage被一個Worker所處理。它既會用來請求檔案讀取,也會用來返回結果,所以它封裝關於從磁碟檔案讀取資料的任何內容,包括實際中資料怎樣被讀取:
// 資料讀取請求: 讀取一個磁碟檔案的內容到快取區.
struct ReadRequest
{
public:
explicit ReadRequest(
const Theron::Address client = Theron::Address(),
const char *const fileName = 0,
unsigned char *const buffer = 0,
const unsigned int bufferSize = 0) :
mClient(client),
mFileName(fileName),
mProcessed(false),
mBuffer(buffer),
mBufferSize(bufferSize),
mFileSize(0)
{
}
void Process()
{
mProcessed = true;
mFileSize = 0;
// 嘗試開啟檔案
FILE *const handle = fopen(mFileName, "rb");
if (handle != 0)
{
// 讀取資料檔案,設定實際讀取長度
mFileSize = (uint32_t) fread(
mBuffer,
sizeof(unsigned char),
mBufferSize,
handle);
fclose(handle);
}
}
Theron::Address mClient; // 請求客戶的地址
const char *mFileName; // 請求檔案的名稱
bool mProcessed; // 檔案是否被讀取過
unsigned char *mBuffer; // 檔案內容的快取區
unsigned int mBufferSize; // 快取區的大小
unsigned int mFileSize; // 檔案的位元組長度
};
我們可以使用任何class或者struct作為Theron中的訊息,唯一需要注意的是需要嚴格要求訊息型別必須安全地可拷貝。當一個訊息被髮送,Theron會首先建立一個新的訊息拷貝,以此傳送端和接收端可以看到不同的拷貝資料,從而避免共用一個訊息記憶體。
實際上訊息可拷貝是有重要的隱含資訊的。它意味著我們訊息必須是輕量級的,否則效能會大大的降低。訊息中通過指標指向檔名和記憶體緩衝器,而不是緩衝器本身,這樣可以避免大量的資料的拷貝(也就是所謂的淺拷貝)。
無論什麼時候我們傳送一個訊息中的指標,我們會訪問actor接收訊息來接近依靠指標指向的記憶體地址。因為傳送者也可以接近這塊記憶體,其實也是潛在的共享記憶體。這看似與我們一直強調的避免共享記憶體相違背,因為這樣做非常危險,但是我們仍然在這裡這樣做是因為使用訊息同步接近共享快取區,可以確保傳送端和接收端並不是同一時刻區接近這塊快取區。
2.3 主函式
訊息與actor已經創立完畢,下面我們來建立一個簡單的main程式來串聯起來執行。首先,我們需要建立一個Worker actor,然後傳送給它一系列ReadRequest型別的訊息(使用命令列讀取的),等待處理結果,最後列印輸出所有讀取檔案的細節:
static const int MAX_FILES = 16;
static const int MAX_FILE_SIZE = 16384;
int main(int argc, char *argv[])
{
if (argc < 2)
{
printf("Expected up to 16 file name arguments.\n");
}
// 建立一個worker去處理工作
Theron::Framework framework;
Worker<ReadRequest> worker(framework);
// 註冊一個receiver來捕(catcher)獲返回的結果
Theron::Receiver receiver;
Theron::Catcher<ReadRequest> resultCatcher;
receiver.RegisterHandler(&resultCatcher, &Theron::Catcher<ReadRequest>::Push);
// 命令列上每個檔名稱作為請求訊息
for (int i = 0; i < MAX_FILES && i + 1 < argc; ++i)
{
unsigned char *const buffer = new unsigned char[MAX_FILE_SIZE];
const ReadRequest message(
receiver.GetAddress(),
argv[i + 1],
buffer,
MAX_FILE_SIZE);
framework.Send(message, receiver.GetAddress(), worker.GetAddress());
}
// 等待所有結果
for (int i = 1; i < argc; ++i)
{
receiver.Wait();
}
// 處理結果,我們僅列印檔案的名稱
ReadRequest result;
Theron::Address from;
while (!resultCatcher.Empty())
{
resultCatcher.Pop(result, from);
printf("Read %d bytes from file '%s'\n", result.FileSize(), result.FileName());
// 釋放申請的空間
delete [] result.Buffer();
}
}
我們再詳細的理一遍整個過程,受我們建立了一個Theron::Framework,這個Framework前面介紹過就是一個管理類來主持actors。
接著我們楚江了一個Worker actor模板例項,例項化ReadRequest訊息型別。我們將該Worker actor和framework進行繫結,從而可以有效的管理framework中的worker。
再接著我們建立了一個Theron::Receiver。這錢Hello world!中已經提及過Receiver是一個幫助類,擁有自己的地址,可以讓非actor的程式碼能夠接收來自actors的訊息(main函式自然不是actor模式建立的,所以需要一個具體地址來接收來自actor的訊息)。Wait()方法可以用來同步等待來自actors的訊息。
和同步訊息一樣,Receiver也允許我們處理和檢查訊息。我們可以像傳統類一樣註冊一個公共的方法作為訊息的處理函式。但是在這個案例中我們使用的是Theron::Catcher,它是另一個幫助者,是一個執行緒安全的佇列可以捕獲被Receiver收到的所有訊息。Catcher的Push()方法可以被註冊為一個Receiver的訊息處理函式。
3 小結
這篇博文我們主要完成了一個讀取檔案功能的應用,我們接觸了三個Theron框架的核心概念:管理actors的Frameworks,讓非actor的程式碼可以接收來自actors的訊息的Receivers;以及儲存接收自Receivers的訊息。
但是這個例項到目前為止有一個缺陷,就是儘管Worker可以在一個獨立執行緒非同步處理ReadRequests訊息,但是它仍然是串聯來處理所有的訊息。當Worker收到一系列ReadRequests訊息的時候,它的Handler()訊息處理函式會嚴格按照順序依次被執行。實際上,這些請求在Worker的內部訊息佇列中排列著。下一篇博文我會擴充套件這個樣例,通過寫一個Dispatcher actor(actor排程員)來建立和控制一系列Workers來同時並行處理多個請求。
以上是個人學習記錄,由於能力和時間有限,如果有錯誤望讀者糾正,謝謝!
轉載請註明出處:http://blog.csdn.net/FX677588/article/details/75201088
參考文獻:
Theron框架官網File reader章節http://www.theron-library.com/index.php?t=page&p=lesson01
相關文章
- Java 併發程式設計 Executor 框架Java程式設計框架
- 《java併發程式設計的藝術》併發容器和框架Java程式設計框架
- 《java併發程式設計的藝術》Executor框架Java程式設計框架
- 深入淺出 Java 併發程式設計 (1)Java程式設計
- 併發程式設計程式設計
- java併發程式設計系列:java併發程式設計背景知識Java程式設計
- Python併發程式設計Python程式設計
- 併發程式設計 synchronized程式設計synchronized
- Go 併發程式設計Go程式設計
- 併發程式設計(ReentrantLock)程式設計ReentrantLock
- Java併發程式設計Java程式設計
- 併發程式設計(四)程式設計
- 併發程式設計(二)程式設計
- golang併發程式設計Golang程式設計
- Golang 併發程式設計Golang程式設計
- 併發程式設計13程式設計
- java 併發程式設計Java程式設計
- 併發程式設計—— LinkedTransferQueue程式設計
- Go 併發程式設計 - 併發安全(二)Go程式設計
- Java併發程式設計(07):Fork/Join框架機制詳解Java程式設計框架
- 併發程式設計:DEMO:比較Stream和forkjoin框架的效率程式設計框架
- Java併發程式設計 - 第十一章 Java併發程式設計實踐Java程式設計
- 【Java併發程式設計】一、為什麼需要學習併發程式設計?Java程式設計
- Python併發程式設計之從效能角度來初探併發程式設計(一)Python程式設計
- 併發程式設計之:JUC併發控制工具程式設計
- Java併發程式設計-鎖及併發容器Java程式設計
- 併發程式設計(二)——併發類容器ConcurrentMap程式設計
- python併發程式設計之程式1(守護程式,程式鎖,程式佇列)Python程式設計佇列
- 併發程式設計 join原理程式設計
- 併發程式設計 棧幀程式設計
- 併發程式設計概覽程式設計
- 併發程式設計-ExecutorCompletionService解析程式設計
- Java 併發程式設計解析Java程式設計
- 併發程式設計進階程式設計
- 併發程式設計之:Atomic程式設計
- 併發程式設計之:ThreadLocal程式設計thread
- 併發程式設計之:Lock程式設計
- 併發程式設計之:ForkJoin程式設計
- python-併發程式設計Python程式設計