原文
C++11
里也能玩無棧協程了?
答案是:可以!
事實上異網
在很早時,C++11
里就可用無棧協程
寫異步代碼
了,只不過用起來不太方便
,來看看C++11
里怎么用異網
無棧協程寫一個回音服務器
的吧.
#包含 <異網.h++>
#包含 <內存>
#包含 <向量>
#包含 <異網/產生.h++>
用 異網::異步寫;
用 異網::緩沖;
用 異網::ip::傳控;
用 異網::錯誤碼;
用 大小型;
構 會話:異網::協程
{共針<傳控::套接字>套接字_;共針<向量<符>>緩沖_;會話(共針<傳控::套接字> 套接字): 套接字_(套接字),緩沖_(新 向量<符>(1024)){}空 符號()(錯誤碼 ec = 錯誤碼(), 大小型 n = 0){如 (!ec) 再入 (本){對 (;;){產生 套接字_->異步讀些(緩沖(*緩沖_), *本); 產生 異步寫(*套接字_, 緩沖(*緩沖_, n), *本); }}}
};
構 服務器 : 異網::協程
{異網::io服務& io服務_;共針<傳控::受者> 受者_;共針<傳控::套接字> 套接字_;服務器(異網::io服務& io服務): io服務_(io服務),受者_(新 傳控::受者(io服務, 傳控::端點(傳控::v4(), 54321))){}空 符號()(錯誤碼 ec = 錯誤碼()){再入 (本){對 (;;){套接字_.重置(新 傳控::套接字(io服務_));產生 受者_->異步接受(*套接字_, *本);io服務_.提交(會話(套接字_));}}}
};
#包含 <異網/堅定.h++>
整 主()
{異網::io服務 io服務;io服務.提交(服務器(io服務));io服務.跑();
}
先看回聲會話
部分的代碼:
空 符號()(錯誤碼 ec = 錯誤碼(), 大小型 n = 0){如 (!ec) 再入 (本){對 (;;){產生 套接字_->異步讀些(緩沖(*緩沖_), *本); 產生 異步寫(*套接字_, 緩沖(*緩沖_, n), *本); }}}
//其中
對 (;;)
{產生 套接字_->異步讀些(緩沖(*緩沖_), *本); 產生 異步寫(*套接字_, 緩沖(*緩沖_, n), *本);
}
和C++20
的協程代碼
是不是很像:
對 (;;)
{協待 套接字_->異步讀些(緩沖(*緩沖_), 異網::用可等待); 協待 異步寫(*套接字_, 緩沖(*緩沖_, n), 異網::用可等待);
}
所以C++11
里面也是可通過協程
來寫異步代碼的.
但是異網
的無棧協程
實現比較簡陋,使用起來有不少約束.首先要把用無棧協程
的文件
用兩個頭文件
包裝起來,因為內部是宏
實現的,編譯完之后需要解定義
.
#包含 <異網/產生.h++>
#包含 <異網/堅定.h++>
第二個不便之處是不自由
,使用協程的對象要從異網::協程
繼承,然后定義符號
,注意該符號
是怎么寫的:
空 符號()(錯誤碼 ec = 錯誤碼(), 大小型 n = 0){如 (!ec) 再入 (本){對 (;;){產生 套接字_->異步讀些(緩沖(*緩沖_), *本); 產生 異步寫(*套接字_, 緩沖(*緩沖_, n), *本); }}}
相信你第一次看到該代碼時會很懵
,這是個啥意思,再入
是什么鬼,看不懂該代碼.不要著急,聽我來給你解惑該符號
.
參數
是錯誤碼
和傳輸大小
,最開始時是默認的,為何這兩個參數
呢?注意函數中的再入(本)
,它表明當前該函數
是可重入
的,則何時
會重入呢?
每次產生
返回時就重入
了,產生
之后ec
和大小
就是新的了.比如產生 套接字_->異步讀些(緩沖(*緩沖_),*本)
;
返回后,就可得到錯誤碼
和讀到的大小
了,所以完整
寫法應該
是:
空 符號()(錯誤碼 ec = 錯誤碼(), 大小型 n = 0){如 (!ec) 再入 (本){對 (;;){產生 套接字_->異步讀些(緩沖(*緩沖_), *本);//協程重入如(ec){輸出<< "讀錯誤消息: " << ec.消息() << "\n";斷;}輸出 << "讀大小: " << n << "\n";產生 異步寫(*套接字_, 緩沖(*緩沖_, n), *本); //協程重入如(ec){輸出<< "寫錯誤消息: " << ec.消息() << "\n";斷;}輸出<<"寫大小:"<<n<<"\n";}}}
現在看懂了,該怪異
寫法了吧.
接著,如果想開啟新協程
要怎么做,看服務器接受
的代碼:
空 符號()(錯誤碼 ec = 錯誤碼()){再入 (本){對 (;;){套接字_.重置(新 傳控::套接字(io服務_));產生 受者_->異步接受(*套接字_, *本);io服務_.提交(會話(套接字_));}}}
在產生受者_->異步接受(*套接字_,*本);
之后通過提交
,把會話
對象丟到io環境
線程池中了,后續會執行該會話
對象,此時,你可能有疑問,為啥可把對象
丟到線程池
里?
答案很簡單,因為會話
有符號
它就成為一個函數對象
了.
為什么這里要把會話
函數對象丟到線程池
里呢,為啥不繼續產生
呢?因為這里想立即
再次接受
而不是等待會話
結束,如果產生 會話
那就沒辦法繼續接受
了.
通過提交
非阻塞方式調用會話
的協程,兩個協程不會相互阻塞
.
除此外,異網
還提供了另外一個創建子協程
的方法:
空 服務器::符號()(異網::錯誤碼 ec, 大小型 長度)
{如 (!ec){再入 (本){干{套接字_.重置(新 傳控::套接字(受者_->取執行器()));產生 受者_->異步接受(*套接字_, *本);分叉 服務器(*本)();//`分叉`之后父子協程都會往下執行到此} 當 (是父());產生 套接字_->異步讀些(緩沖(*緩沖_), *本); 產生 異步寫(*套接字_, 緩沖(*緩沖_, n), *本); }}
}
異網
可通過分叉
來創建子協程,分叉
之后父協程和子協程
都會往下執行,那如何區分
父子協程呢?通過是父()
來區分.
父協程是接受
協程,當走到是父()
時,它會繼續接受
,而子協程則會跳出當
循環,往下去讀
和寫
了.
這就是通過分叉
創建新協程
的方法.
總結
異網
里面有很多有趣的東西,以前還沒注意C++11
里用可無棧協程
寫異步代碼
,也是其余遠調用
維護時想用協程去改造,然后發現了該東西,它已有很久的歷史了,但我現在才注意到,確實能簡化編寫異步代碼
,后面C++11
版本的其余遠調用
會使用異網
無棧協程去寫.
不過寫法比使用C++20
協程庫麻煩多了,但是考慮畢竟是C++11
,能用協程
已不錯了,如果能升級到20
標準還是用20
協程舒服.