C++併發程式設計框架Theron(6)——File reader(2)

無鞋童鞋發表於2017-07-17

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/

相關文章