C++併發程式設計框架Theron(6)——File reader(2)
1 前言
在上一篇博文中我主要介紹了一個非同步功能的檔案讀取服務程式,但是程式有一點小小的不足就是不能並行處理多檔案:儘管檔案請求是在一個獨立執行緒下非同步處理的,但是他們仍然是嚴格按先後順序來執行操作的。在這篇博文中,我們將使用一系列執行緒Workers去併發處理讀取請求,而不是僅僅一個非同步的執行緒。在本文當中,我們會學到怎樣從actors集合中構建一個複雜的系統,並且使用actor抽象化隱藏在訊息傳遞交流的背後的執行緒操作。
2 多個actors
為了整合一系列Worker actors,我們需要一箇中心分發功能的actor來管理這些workers,並且有序派遣Readquests給空閒的workers:
// 一個派遣actor處理工作事例
// 派遣actor會在內部建立和控制一系列workers.
// 它協調workers並行處理工作事例
template <class WorkMessage>
class Dispatcher : public Theron::Actor
{
public:
Dispatcher(Theron::Framework &framework, const int workerCount) : Theron::Actor(framework)
{
// 建立workers並且將它們加入空閒的列中
for (int i = 0; i < workerCount; ++i)
{
mWorkers.push_back(new WorkerType(framework));
mFreeQueue.push(mWorkers.back()->GetAddress());
}
RegisterHandler(this, &Dispatcher::Handler);
}
~Dispatcher()
{
// 釋放workers
const int workerCount(static_cast<int>(mWorkers.size()));
for (int i = 0; i < workerCount; ++i)
{
delete mWorkers[i];
}
}
private:
typedef Worker<WorkMessage> WorkerType;
// 從客戶端處理讀取請求
void Handler(const WorkMessage &message, const Theron::Address from)
{
// 是否這個工作事例被處理過?
if (message.Processed())
{
// 傳送結果返回給請求的呼叫者
Send(message, message.Client());
// 將worker加入到空間的列中
mFreeQueue.push(from);
}
else
{
// 將沒有處理的工作事例放入工作列中
mWorkQueue.push(message);
}
// 服務於工作佇列
while (!mWorkQueue.empty() && !mFreeQueue.empty())
{
Send(mWorkQueue.front(), mFreeQueue.front());
mFreeQueue.pop();
mWorkQueue.pop();
}
}
std::vector<WorkerType *> mWorkers; // 擁有的workers的指標
std::queue<Theron::Address> mFreeQueue; // 空閒workers佇列.
std::queue<WorkMessage> mWorkQueue; // 未處理工作訊息佇列
};
這塊我還要說明一下,我們建立的Dispatcher是作為一個類别範本,來強調就像worker一樣,它並不是僅僅用來讀取檔案,還可以用來並行處理其他形式的工作。
Workers群體是使用new運算子在構造器中被動態建立的,儲存在一個指標向量中,並且是在解構函式中使用delete運算子被銷燬。
構造器也將workers的地址推送進空閒的worker佇列中,可以根據它們的地址被識別。這個佇列被用來記錄哪些workers在當前是空閒的,並且可以用來處理work佇列中收到的工作訊息。
實際的行動是在Handler()方法中。當收到一條WorkMessage,Handler()方法會檢查該訊息是不是已經看過。如果是新訊息會被放置到工作佇列中,不然就直接返回之前檢測的結果給請求的客戶端。
當Handler()方法反饋完一個結果訊息,它會重新將這個worker的地址傳送加到空閒workers佇列中,從而它可以被用來服務於未來的工作請求。最終整個過程形成了一個工作服務環結構,即我們使用空閒worker佇列中的空閒的workers服務於work佇列中未處理的工作訊息。
3 多個frameworks
在上面介紹中我們已經瞭解到,Theron::Framework是一個管理類,用來主持actors集合。在這一小節我會展現Frameworks是如何可以用來控制執行緒來執行檔案讀取請求的。
多個frameworks在一個單應用中被建立,並且管理著各自的actors集體。在每個frameworks內部,每個framework例項使用自己的worker執行緒集合來執行它管理的actors的訊息處理函式(之前理論中有介紹過)。
從訊息傳遞來看,frameworks之間的壁壘如同透明般一樣:在一個framework中的actors完全可以給其他frameworks的actors傳送訊息,就像一個framework中兩個actor之間通訊那樣簡單。Actor地址是全域性獨一無二的,不僅可以用來分別出actor,而且可以分別擁有它的framework,所以不同frameworks中的actors通訊暢行無阻。
到目前為止我們介紹的frameworks都是依靠預設建構函式建立的。但是也可以提供一個Parameters構造器parameter,允許控制worker執行緒數量和哪個處理器核被執行。
Theron::Framework::Parameters frameworkParams;
frameworkParams.mThreadCount = 16;
frameworkParams.mProcessorMask = 0xF;
Theron::Framework framework(frameworkParams);
通過設定這個framework中執行緒池的大小,我們可以配置軟體同時接觸其管理的actors的數量。此外,通過限制一個framework的worker執行緒集被執行佔用系統核的資源量,我們可以確保其他剩餘系統資源可以用來處理事,包括非actor程式碼和其他frameworks的actors。
main()函式如下,我們也構建一個Framework用來完成檔案讀取服務:
int main(int argc, char *argv[])
{
// Theron::Framework物件可以被有參構造
// 我們建立一個擁有n個執行緒的framework, n是檔案的數量,我們希望可以並行讀取所有檔案。
// 我們也限制工作執行緒為前兩個處理器核
Theron::Framework::Parameters frameworkParams;
frameworkParams.mThreadCount = MAX_FILES;
frameworkParams.mProcessorMask = (1UL << 0) | (1UL << 1);
Theron::Framework framework(frameworkParams);
if (argc < 2)
{
printf("Expected up to 16 file name arguments.\n");
}
// 註冊一個receiver和一個處理函式,用來捕獲結果訊息
Theron::Receiver receiver;
Theron::Catcher<ReadRequest> resultCatcher;
receiver.RegisterHandler(&resultCatcher, &Theron::Catcher<ReadRequest>::Push);
// 建立一個dispatcher用來處理工作, 一共有MAX_FILES個workers.
Dispatcher<ReadRequest> dispatcher(framework, MAX_FILES);
// 傳送工作請求, 每個工作請求是命令列上輸入的檔名
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(), dispatcher.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();
}
}
我們使用mThreadCount個Parameters結構來具體化framework中worker執行緒的數量。建立更多的執行緒允許更多的actors可以在同一時刻被執行,但也會增加開銷。通過具體化MAX_FILES作為工作執行緒總量,我們支援同時處理MAX_FILES閱讀操作。
而在指定系統處理該程式的處理器核方面,我們使用mProcessMask成員來指定工作執行緒的處理器親暱度。親暱度是按位被指定的(每一位對應一個處理器)。我們假設現在的硬體系統都包含至少兩個核,我們限制IO framework在前兩個,剩下其他核給其他frameworks。因為這個僅是一個例項,我們實際上不需要建立任何其他的actors,但是如果我們需要的話,我們還要建立它們在另一個獨立的Framework。
4 小結
因為系統檔案的讀取呼叫可以阻塞ms級甚至s級的開銷。通過在一個獨立Framework隔離檔案讀取服務的actors,我們可以貢獻一個執行緒池來執行這些潛在的檔案閱讀。並且通過具體化framework的執行緒池,我們可以控制多少執行緒被允許在此被佔用。接著,甚至所有的執行緒都被突如其來的許多並行檔案讀取請求所佔用,我們知道其他frameworks的工作執行緒也不會受到任何影響,所以他們的actors並不會被餓死。
以上是個人學習記錄,由於能力和時間有限,如果有錯誤望讀者糾正,謝謝!
轉載請註明出處:http://blog.csdn.net/FX677588/article/details/75268270
參考文獻:
Theron框架官網http://www.theron-library.com/
相關文章
- Java 併發程式設計 Executor 框架Java程式設計框架
- 併發程式設計-6.並行程式設計概念程式設計並行行程
- 《java併發程式設計的藝術》併發容器和框架Java程式設計框架
- 《java併發程式設計的藝術》Executor框架Java程式設計框架
- [仁潤雲技術團隊]併發程式設計-(2)併發程式設計的目標程式設計
- 深入淺出 Java 併發程式設計 (2)Java程式設計
- 併發程式設計程式設計
- 2. Go併發程式設計--GMP排程Go程式設計
- 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程式設計
- 併發程式設計 join原理程式設計
- 併發程式設計 棧幀程式設計
- 併發程式設計概覽程式設計
- 併發程式設計-ExecutorCompletionService解析程式設計
- Java 併發程式設計解析Java程式設計
- 併發程式設計進階程式設計
- 併發程式設計之:Atomic程式設計
- 併發程式設計之:ThreadLocal程式設計thread
- 併發程式設計之:Lock程式設計