ポータブルにできるとは思えませんが、解決策はありますか? 代替スタックを作成し、関数エントリで SP、BP、および IP をリセットし、IP を保存して SP + BP を復元することで実行できると思います。デストラクタと例外の安全性は難しいように見えますが、解決可能です。
それは行われましたか?無理ですか?
はい、問題なくできます。必要なのは、コール スタックをヒープ上の新しく割り当てられたスタックに移動するための小さなアセンブリ コードだけです。
boost::coroutineライブラリを見てみましょう。
注意すべきことの 1 つは、スタック オーバーフローです。ほとんどのオペレーティング システムでは、仮想メモリ ページがマップされていないため、スタックがオーバーフローするとセグメンテーション違反が発生します。ただし、スタックをヒープに割り当てると、保証は得られません。それを覚えておいてください。
POSIX では、makecontext()/swapcontext() ルーチンを使用して実行コンテキストを移植可能に切り替えることができます。Windows では、ファイバー API を使用できます。それ以外の場合は、マシン コンテキストを切り替えるちょっとした接着アセンブリ コードが必要です。ASM (AMD64 用) と swapcontext(); の両方でコルーチンを実装しました。どちらも非常に難しいことではありません。
コルーチンを実装する簡単な方法はありません。コルーチン自体は、スレッドと同様に 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 の代わりに、再開可能と await になります。
Visual Studio 2015 の実験的実装を見て、Microsoft の実験的実装を試してみてください。clang にはまだ実装がないようです。
Cppconコルーチンからの良い話があります。負のオーバーヘッドの抽象化は、C++ でコルーチンを使用する利点と、それがコードの単純さとパフォーマンスにどのように影響するかを概説しています。
現時点では、まだライブラリの実装を使用する必要がありますが、近い将来、コア C++ 機能としてコルーチンを使用する予定です。
更新: コルーチンの実装は C++20 向けに予定されているようですが、C++17 ( p0057r2 ) の技術仕様としてリリースされました。Visual C++、clang、および gcc では、コンパイル時フラグを使用してオプトインできます。
C++ には本格的でクリーンな実装がたくさんあるとは思いません。私が気に入っている試みの 1 つは、Adam Dunkels の protothread ライブラリです。
Protothreads: simpleing event-driven programming of memory-constrained embedded systemsの ACM Digital Library およびウィキペディアのトピックProtothreadでの議論も参照してください。
コルーチン シーケンス用の移植可能な C++ ライブラリである COROUTINEは正しい方向を示していますか? それは時の試練に耐えてきたエレガントなソリューションのように思えます..それは9歳です!
DOC フォルダには、Keld Helsgaun による論文 A Portable C++ Library for Coroutine Sequencing の PDF があり、ライブラリについて説明し、それを使用した短い例を提供しています。
【更新】実際に私自身もうまく使っています。好奇心が勝ったので、この解決策を調べたところ、私がしばらく取り組んできた問題にぴったりだとわかりました。
コルーチンを実装するための移植可能な機能を備えた新しいライブラリBoost.Contextが本日リリースされました。
これは (cringe) マクロに基づいていますが、次のサイトでは使いやすいジェネレーターの実装が提供されています: http://www.codeproject.com/KB/cpp/cpp_generators.aspx
これは古いスレッドですが、OS に依存しない (覚えている限り) Duff のデバイスを使用したハックを提案したいと思います。
例として、フォーク/スレッドの代わりにコルーチンを使用するように変更した Telnet ライブラリを次に示します。 コルーチンを使用する Telnet cli ライブラリ
また、C99 より前の標準 C は本質的に C++ の真のサブセットであるため、これは C++ でもうまく機能します。
asmコードを使用しない実装を考え出しました。アイデアは、システムのスレッド作成関数を使用してスタックとコンテキストを初期化し、setjmp/longjmp を使用してコンテキストを切り替えることです。ただし、移植性はありません。興味がある場合は、トリッキーな pthread バージョンを参照してください。
https://github.com/tonbit/coroutineは、resume/yield/await プリミティブと Channel モデルをサポートする C++11 単一の .h 非対称コルーチン実装です。ブーストに依存せず、ucontext / fiber 経由で実装し、linux/windows/macOS で動作します。C++ でのコルーチンの実装を学ぶための良い出発点です。
私の実装をチェックしてください。それは asm ハッキングポイントを示しており、簡単です:
https://github.com/user1095108/generic/blob/master/coroutine.hpp
WvContは、いわゆるセミコルーチンを実装するWvStreamsの一部です。これらは、完全なコルーチンよりも処理が少し簡単です。呼び出すと、それを呼び出した人に返されます。
完全なコルーチンをサポートする、より柔軟な WvTask を使用して実装されます。同じライブラリで見つけることができます。
少なくとも win32 と Linux で動作し、おそらく他の Unix システムでも動作します。
代わりにスレッドの使用を常に検討する必要があります。特に最新のハードウェアでは。コルーチンで論理的に分離できる作業がある場合、スレッドを使用すると、実際には作業が別々の実行ユニット (プロセッサ コア) によって同時に実行される可能性があります。
しかし、おそらくコルーチンを使用したい場合があります。おそらく、その方法で作成およびテストされた十分にテストされたアルゴリズムがあるため、またはその方法で作成されたコードを移植しているからです。
Windows で作業している場合は、fiberを参照してください。ファイバーは、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();
}
}