C++ でコルーチンを実装するにはどうすればよいですか
質問
移植可能であるとは思えませんが、解決策はありますか?代替スタックを作成し、関数エントリ時に SP、BP、IP をリセットし、IP を保存して SP+BP を復元することで実現できると思います。デストラクターと例外の安全性は難しいように思えますが、解決可能です。
それは行われましたか?それは不可能ですか?
解決
はい、それ できる 問題なく。必要なのは、呼び出しスタックをヒープ上に新しく割り当てられたスタックに移動するための小さなアセンブリ コードだけです。
私は...するだろう 見てください ブースト::コルーチン 図書館.
注意しなければならないのはスタック オーバーフローです。ほとんどのオペレーティング システムでは、仮想メモリ ページがマップされていないため、スタックがオーバーフローするとセグメンテーション違反が発生します。ただし、スタックをヒープに割り当てた場合、保証はありません。それだけは覚えておいてください。
他のヒント
POSIX では、makecontext()/swapcontext() ルーチンを使用して、実行コンテキストを移植可能に切り替えることができます。Windows では、ファイバー API を使用できます。それ以外の場合、必要なのは、マシンのコンテキストを切り替える少しの接着アセンブリ コードだけです。ASM (AMD64 用) と swapcontext() の両方でコルーチンを実装しました。どちらもそれほど難しいことではありません。
後世のために、
ドミトリー・ヴュコフの 素晴らしいウェブサイト ucontext と setjump を使用して C++ でコルーチンをシミュレートする賢いトリックがあります。
また、Oliver Kowalke のコンテキスト ライブラリは 最近受け入れられた Boost に組み込まれているため、x86_64 で動作する boost.coroutine の更新バージョンがすぐに登場することを期待しています。
コルーチンを実装する簡単な方法はありません。コルーチン自体はスレッドと同様に C/C++ のスタック抽象化の外にあるためです。したがって、サポートする言語レベルを変更しないとサポートできません。
現在 (C++11)、既存の C++ コルーチン実装はすべてアセンブリ レベルのハッキングに基づいており、プラットフォームを越えて安全かつ信頼性の高いものにするのは困難です。信頼性を高めるには、標準であり、ハッキングではなくコンパイラーによって処理される必要があります。
あります 標準提案 - N3708 このために。興味があればチェックしてみてください。
可能であれば、コルーチンよりもイテレーターを使用した方が良いかもしれません。そうすれば通話を続けることができます next()
次の値を取得しますが、状態をローカル変数ではなくメンバー変数として保持することもできます。
そうすることで、より保守しやすくなるかもしれません。別の C++ 開発者は、コルーチンをすぐには理解できないかもしれませんが、イテレータについてはよく知っているかもしれません。
C++ で移植可能な方法でコルーチンを活用する方法を知りたい人は、y̶o̶u̶ ̶w̶i̶l̶l̶ ̶h̶a̶v̶e̶ ̶t̶o̶ ̶w̶a̶i̶t̶ ̶f̶o̶r̶ ̶C̶+̶+̶1̶7̶ 待つ必要はありません (以下を参照)。 !標準委員会がこの機能に取り組んでいます。を参照してください。 N3722用紙. 。この論文の現在の草案を要約すると、キーワードは Async と Await の代わりに、resumeable と await になります。
Microsoft の実験的実装を試してみるには、Visual Studio 2015 の実験的実装を見てください。Clang にはまだ実装がないようです。
Cppconから良い話があります コルーチンは負のオーバーヘッドの抽象化です C++ でコルーチンを使用する利点と、それがコードの単純さとパフォーマンスにどのような影響を与えるかを概説します。
現時点ではまだライブラリ実装を使用する必要がありますが、近い将来、コア C++ 機能としてコルーチンが導入される予定です。
アップデート:コルーチンの実装は C++20 向けに予定されているようですが、C++17 で技術仕様としてリリースされました (p0057r2)。Visual C++、clang、gcc では、コンパイル時フラグを使用してオプトインできます。
する COROUTINE コルーチン シーケンス用の移植可能な C++ ライブラリ 正しい方向を示してくれますか?それは時の試練を経たエレガントなソリューションのように思えます....それは 9 年前のものです。
DOC フォルダーには、Keld Helsgaun による論文「A Portable C++ Library for Coroutine Sequencing」の PDF があり、ライブラリについて説明し、それを使用した短い例が示されています。
[更新] 私自身も実際にそれをうまく活用しています。好奇心に負けてこの解決策を調べたところ、私がしばらく取り組んできた問題にぴったりであることがわかりました。
C++ には本格的でクリーンな実装があまりないと思います。私が気に入っている試みの 1 つは、 Adam Dunkels のプロトスレッド ライブラリ.
こちらも参照 プロトスレッド:メモリに制約のある組み込みシステムのイベント駆動型プログラミングを簡素化する ACM デジタル ライブラリおよび Wikipedia トピックのディスカッション プロトスレッド,
新しい図書館、 ブーストコンテキスト, 、コルーチンを実装するためのポータブル機能を備えたものが本日リリースされました。
これは古いスレッドですが、(私が覚えている限りでは)OS に依存しない Duff のデバイスを使用したハックを提案したいと思います。
例として、フォーク/スレッドの代わりにコルーチンを使用するように変更した Telnet ライブラリを次に示します。コルーチンを使用した Telnet cli ライブラリ
また、C99 より前の標準 C は本質的に C++ の真のサブセットであるため、これは C++ でもうまく機能します。
これは (クリンジ) マクロに基づいていますが、次のサイトでは使いやすいジェネレーターの実装が提供されています。 http://www.codeproject.com/KB/cpp/cpp_generators.aspx
実装を思いつきました asmなし コード。このアイデアは、システムのスレッド作成関数を使用してスタックとコンテキストを初期化し、setjmp/longjmp を使用してコンテキストを切り替えることです。ただし、ポータブルではありません。を参照してください。 トリッキーな pthread バージョン もし興味があれば。
https://github.com/tonbit/coroutine C++11 の単一の .h 非対称コルーチン実装で、resume/yield/await プリミティブとチャネル モデルをサポートします。ブーストに依存せず、ucontext / ファイバー経由で実装され、linux/windows/macOS 上で実行されます。これは、C++ でのコルーチンの実装を学習するための良い出発点となります。
私の実装をチェックしてください。これは asm ハッキングポイントを示しており、簡単です。
https://github.com/user1095108/generic/blob/master/coroutine.hpp
代わりにスレッドの使用を常に検討する必要があります。特に最新のハードウェアでは。コルーチンで論理的に分離できる作業がある場合、スレッドを使用すると、その作業が実際には別々の実行ユニット (プロセッサ コア) によって同時に実行される可能性があります。
しかし、コルーチンを使用したいと思うかもしれません。おそらく、すでにそのように記述されテストされている十分にテストされたアルゴリズムがあるため、またはそのように記述されたコードを移植しているためです。
Windows 内で作業している場合は、以下を参照してください。 繊維. 。Fibers は、OS のサポートを備えたコルーチンのようなフレームワークを提供します。
他の OS については詳しくないので、代替手段を推奨することはできません。
C++11 とスレッドを使用して自分でコルーチンを実装しようとしました。
#include <iostream>
#include <thread>
class InterruptedException : public std::exception {
};
class AsyncThread {
public:
AsyncThread() {
std::unique_lock<std::mutex> lock(mutex);
thread.reset(new std::thread(std::bind(&AsyncThread::run, this)));
conditionVar.wait(lock); // wait for the thread to start
}
~AsyncThread() {
{
std::lock_guard<std::mutex> _(mutex);
quit = true;
}
conditionVar.notify_all();
thread->join();
}
void run() {
try {
yield();
for (int i = 0; i < 7; ++i) {
std::cout << i << std::endl;
yield();
}
} catch (InterruptedException& e) {
return;
}
std::lock_guard<std::mutex> lock(mutex);
quit = true;
conditionVar.notify_all();
}
void yield() {
std::unique_lock<std::mutex> lock(mutex);
conditionVar.notify_all();
conditionVar.wait(lock);
if (quit) {
throw InterruptedException();
}
}
void step() {
std::unique_lock<std::mutex> lock(mutex);
if (!quit) {
conditionVar.notify_all();
conditionVar.wait(lock);
}
}
private:
std::unique_ptr<std::thread> thread;
std::condition_variable conditionVar;
std::mutex mutex;
bool quit = false;
};
int main() {
AsyncThread asyncThread;
for (int i = 0; i < 3; ++i) {
std::cout << "main: " << i << std::endl;
asyncThread.step();
}
}