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

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

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

相關文章