儘管可以有不同的翻譯,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