Boost中的協程—Boost.Coroutine2

儘管可以有不同的翻譯,coroutine本文被叫作協程。

概念
Coroutine是相對於routine(過程)而提出的,它與一般的父程序調用子程序的routine不同之處在於,它允許掛起一個程序執行點,過後再從掛起的地方繼續運行,是一種更高級的程序運行順序控制。爲保證程序掛起再恢復,協程有其自己的棧和控制塊,記錄掛起時的狀態。

協程有如下特點:

同其他數據類型一樣,協程也是第一類(first-class)對象,可以被當參數傳遞等操作;
運行特點是掛起運行,離開協程,過後再進入,恢復運行;
具有對稱和非對稱的轉移控制機制
掛起前和恢復後本地變量的值是一致的;
有stackless和stackful兩種類型
對稱轉移是指所有協程都是對等的,協程控制權跳轉時,必須顯式指明將控制權轉給誰。非對稱轉移是指協程記錄調用者,因此可以隱式轉移控制權(給調用者)。

stackful協程能從嵌套的棧結構中恢復,而stackless協程,因爲沒有保存棧,因而只能在最上層(top-level)的過程中使用,被最上層過程調用的過程就不能再掛起了,也就是不能有兩個協程同時被掛起。

爲實現協程,一些語言特別引入了關鍵字,在語言層面支持協程,但目前C++不是如此,例如下文的Boost.Asio中,雖然定義了yield,但其實是一個宏。

 

協程與回調函數
採用協程與採用回調通常被用來對比。而它們通常與事件驅動式編程有關。

事件驅動式編程方式如下圖:

些,但會使代碼執行碎片化—將一個連續過程切成若干hanlder,執行過程在handler函數間跳來跳去,同時,一些本應統一的處理,如錯誤、狀態,就不得不深入每個handler,使得編程繁複,不直觀。

 

Boost.Corountine2
Corountine2是相對於Corountine而言的,在Boost v1.59被引入,Boost.Corountine目前已被標記爲deprecated,因此不再提及。

Boost.Corountine2使用了Boost.Context,因此要使用Boost.Corountine2,必須先編譯Boost.Context。

Boost.Corountine2幾個特徵:

非對稱轉移控制(放棄了對稱轉移)
stackful
對象只能移動(moveable),不能拷貝(copyable),因爲協程對象控制的有些資源,如棧,只能獨享
coroutine<>::push_type 和coroutine<>::pull_type保存棧使用的是塊式內存,可動態擴展,因此不用關心初始棧的大小,在析構時,所有的內存都被釋放。
上下文切換通過coroutine<>::push_type::operator() ,coroutine<>::pull_type::operator()來完成,因此這兩個函數內部不能再調用自身。
有兩個創建協程的類:coroutine<>::pull_type和coroutine<>::push_type,pull的意思是從主運行環境可以“拉”數據到協程環境,push的意思是從協程環境將數據“推”到主運行環境中,coroutine<>::pull_type拉數據的方法是get(),coroutine<>::push_type推數據的方法是operator(),即在協程中,pull_type.get()可以得到外部傳入的數據,而push_type()可以將數據傳遞到外部環境。爲此,coroutine<>::pull_type提供了輸入iterator及重載了std::begin()/std::end(),coroutine<>::push_type提供了輸出iterator及重載了std::begin()/std::end(),方便它們的循環調用。

示例1如下。

int main()
{
    typedef boost::coroutines2::coroutine<int>   coro_t2;
    std::cout<< "start corountine" << std::endl;    
    coro_t2::pull_type source( // constructor enters coroutine-function
         [&](coro_t2::push_type& sink){
            std::cout<< " sink1" << std::endl;    
            sink(1); // push {1} back to main-context
            std::cout<< " sink2" << std::endl;
            sink(2); // push {2} back to main-context
            std::cout<< " sink3" << std::endl;
            sink(3); // push {3} back to main-context
        });
    std::cout<< "start while" << std::endl;    
    while(source)    // test if pull-coroutine is valid
    {
        int ret=source.get(); // pushed data,that is the argument of sink()
        std::cout<< "move to coroutine-function "<< ret << std::endl;
        source();             // context-switch to coroutine-function
        std::cout<< "back from coroutine-function "<< std::endl;
}
return 0;
}
 
輸出爲:
start corountine
 sink1
start while
move to coroutine-function 1
 sink2
back from coroutine-function
move to coroutine-function 2
 sink3
back from coroutine-function
move to coroutine-function 3
back from coroutine-function
該示例以coroutine<>::pull_type創建協程,以coroutine<>::push_type作爲協程函數的參數,由於主環境中是pull,因此主環境中可以得到協程傳出的數據,而主程序的數據只能通過全局變量來傳遞。程序跳轉的變化通過輸出可以清晰看出來:協程函數中的push函數(即sink)就是跳轉點,下次再次進入協程函數時接着運行,主程序的跳轉點就是pull函數(即source)。

示例2如下。

int main()
{
    typedef boost::coroutines2::coroutine<int>   coro_t3;
    std::cout<< "start corountine" << std::endl;    
    coro_t3::push_type sink( 
         [&](coro_t3::pull_type& source){
            int num;
            num=source.get();
            std::cout<< " source1: "<<num << std::endl;    
            source();   //back to main-context
            std::cout<< " source2: "<<num << "   "<<source.get()<< std::endl;
            source();
            std::cout<< " source3: "<<num << "   "<<source.get()<< std::endl;
            source();
        });
    std::cout<< "start while" << std::endl;    
    int i=1;
    while(sink)    // test if pull-coroutine is valid
    {
        std::cout<< "move to coroutine-function "<<i << std::endl;
        sink(i);             // context-switch to coroutine-function
        std::cout<< "back from coroutine-function "<<i << std::endl;
        i++;
}
return 0;
}
 
輸出爲:
start corountine
start while
move to coroutine-function 1
 source1: 1
back from coroutine-function 1
move to coroutine-function 2
 source2: 1   2
back from coroutine-function 2
move to coroutine-function 3
 source3: 1   3
back from coroutine-function 3
move to coroutine-function 4
back from coroutine-function 4
該示例以coroutine<>::push_type創建協程,以coroutine<>::pull_type作爲協程函數的參數,由於主環境中是push,因此主環境中可以將數據傳遞到協程,而協程數據只能通過全局變量來傳遞。注意到協程中的變量num的狀態在運行中是得到保存的。程序的跳轉通過輸出也可以清晰看出,source/sink就是跳轉點。該示例與示例1其實非常類似,說明coroutine<>::push_type和coroutine<>::pull_type其實是對等的,應用時注意兩個區別:

數據傳輸方式不同,coroutine<>::pull_type用get(),而coroutine<>::push_type直接用參數。
coroutine<>::pull_type在構造函數時,調用了一次協程函數(注意示例1的輸出),而coroutine<>::pull_type沒有。
總結
協程的出現,改變了函數只有唯一運行入口的觀點,一個較長的過程,尤其是過程中存在需要等待的步驟,也可以用一個函數來編寫,等待的時候“跑”到函數外乾點別的,然後再回來接着原來的步驟繼續運行,如果採用回調的方式,需將這個過程切成一塊一塊的,塊與塊之間可以做點別的事情。

協程的出現使我們的編程方式多了一種選擇,但協程的編程,尤其是與一些I/O異步操作結合起來用時,難度還是比較大的,因此我們通常並不直接使用coroutine庫,Boost.Asio已經爲我們封裝了協程的功能,直接用Boost.Asio就好了
————————————————
版權聲明:本文爲CSDN博主「guxch」的原創文章,遵循 CC 4.0 BY-SA 版權協議,轉載請附上原文出處鏈接及本聲明。
原文鏈接:https://blog.csdn.net/guxch/article/details/82803769

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章