web-dev-qa-db-ja.com

なぜコルーチンが戻ってきたのですか?

コルーチンのほとんどの基礎は60年代/​​ 70年代に発生し、その後、代替手段(スレッドなど)のために停止しました。

pythonや他の言語で発生しているコルーチンへの新たな関心に物質はありますか?

21
user1787812

コルーチンは去りませんでしたが、その間、他のものが影を落としました。最近非同期プログラミングへの関心が高まったため、コルーチンは主に3つの要因によるものです。関数型プログラミング手法の受け入れの増加、真の並列処理(JavaScript!Python!)のサポートが不十分なツールセット、そして最も重要なことは、スレッドとコルーチン間の異なるトレードオフです。一部のユースケースでは、コルーチンが客観的に優れています。

80年代、90年代、そして今日の最大のプログラミングパラダイムの1つはOOPです。 OOP=の履歴、特にSimula言語の開発を見ると、クラスがコルーチンから進化したことがわかります。Simulaは、離散イベントのあるシステムのシミュレーション用に設計されました。の各要素システムは、1つのシミュレーションステップの間、イベントに応答して実行され、他のプロセスが機能するようにするための独立したプロセスでした。Simula 67の開発中に、クラスの概念が導入されました。コルーチンの永続的な状態は、オブジェクトメンバーに格納され、メソッドを呼び出すことによってイベントがトリガーされます。詳細については、論文を読むことを検討してくださいNygaard&によるSIMULA言語の開発ダール。

面白いことに、私たちはずっとコルーチンを使用してきました。オブジェクトとイベント駆動型プログラミングと呼んでいただけです。

並列処理に関しては、2種類の言語があります。適切なメモリモデルを持つ言語と持たない言語です。メモリモデルでは、「変数に書き込み、その後、別のスレッドでその変数から読み取った場合、古い値または新しい値、あるいは無効な値が表示されますか?」 「前」と「後」はどういう意味ですか?どの操作がアトミックであることが保証されていますか?」

優れたメモリモデルを作成するのは難しいため、Perl、JavaScript、Python、Ruby、PHPなど、これらの不特定の実装定義の動的オープンソース言語のほとんどに対して、この取り組みは行われていません。もちろん、これらの言語はすべて、元々構築された「スクリプト」をはるかに超えて進化しました。まあ、これらの言語のいくつかはある種のメモリモデルドキュメントを持っていますが、それらは十分ではありません。代わりに、ハックがあります。

  • Perlはスレッディングサポートを使用してコンパイルできますが、各スレッドには完全なインタープリター状態の個別のクローンが含まれているため、スレッドは非常に高価になります。唯一の利点として、このシェアードナッシングアプローチはデータ競合を回避し、プログラマーにキュー/シグナル/ IPCを介してのみ通信することを強制します。 Perlには、非同期処理に関する強力なストーリーはありません。

  • JavaScriptは常に関数型プログラミングを豊富にサポートしているため、プログラマーは非同期操作が必要なプログラムで継続/コールバックを手動でエンコードしていました。たとえば、Ajaxリクエストやアニメーションの遅延。 Webは本質的に非同期であるため、多くの非同期JavaScriptコードがあり、これらすべてのコールバックの管理は非常に困難です。したがって、これらのコールバックをよりよく整理する(Promise)、またはそれらを完全に排除するための多くの努力が見られます。

  • Pythonには、グローバルインタープリターロックと呼ばれるこの残念な機能があります。基本的にPythonメモリモデルは、「並列処理がないため、すべての効果が順番に表示されます。一度に実行されるスレッドは1つだけですPythonコード。)だから、= Pythonにはスレッドがあります。これらはコルーチンと同じくらい強力です。[1] Pythonは、yieldを使用してジェネレーター関数を介して多くのコルーチンをエンコードできます。適切に使用すれば、これだけで、JavaScriptで知られているほとんどのコールバック地獄を回避できます。最新の非同期/待機システムからPython 3.5は、Pythonで非同期イディオムをより便利にし、イベントループを統合します。

    [1]:技術的にこれらの制限はCPython、Python参照実装にのみ適用されます。Jythonのような他の実装は、並行して実行できる実際のスレッドを提供しますが、同等のものを実装するには長い時間を経る必要があります基本的に:すべての変数またはオブジェクトメンバーはvolatile変数であるため、すべての変更はアトミックであり、すべてのスレッドですぐに確認できます。もちろん、volatile変数は、通常の変数を使用するよりもはるかに高価です。

  • RubyとPHPを適切にローストするための十分な知識がありません。

要約すると、これらの言語の一部には、マルチスレッド化を望ましくない、または不可能にする基本的な設計上の決定があるため、コルーチンなどの代替手段や、非同期プログラミングをより便利にする方法に重点が置かれています。

最後に、コルーチンとスレッドの違いについて話しましょう。

スレッドは基本的にプロセスに似ていますが、プロセス内の複数のスレッドがメモリ空間を共有する点が異なります。これは、スレッドがメモリの点で決して「軽量」ではないことを意味します。スレッドは、オペレーティングシステムによって事前にスケジュールされます。つまり、タスクの切り替えはオーバーヘッドが大きく、不都合なときに発生する可能性があります。このオーバーヘッドには、スレッドの状態を一時停止するコストと、ユーザーモード(スレッドの場合)とカーネルモード(スケジューラの場合)を切り替えるコストの2つの要素があります。

プロセスが独自のスレッドを直接かつ協調的にスケジュールする場合、カーネルモードへのコンテキストの切り替えは不要であり、タスクの切り替えは間接関数呼び出しに比べて比較的高価です。これらの軽量スレッドは、さまざまな詳細に応じて、グリーンスレッド、ファイバー、またはコルーチンと呼ばれることがあります。緑のスレッド/ファイバーの注目すべきユーザーは、初期のJava実装であり、最近ではGolangのGoroutineでした。コルーチンの概念的な利点は、実行が明示的にやり取りされる制御フローの観点から理解できることです。ただし、これらのコルーチンは、複数のOSスレッドにまたがってスケジュールされない限り、真の並列処理を実現しません。

安いコルーチンはどこで役に立ちますか?ほとんどのソフトウェアは膨大な数のスレッドを必要としないため、通常の高価なスレッドで通常は問題ありません。ただし、非同期プログラミングによってコードが簡略化される場合があります。自由に使用するには、この抽象化は十分に安価でなければなりません。

そしてWebがあります。上記のように、Webは本質的に非同期です。ネットワーク要求は単に長い時間がかかります。多くのWebサーバーは、ワーカースレッドでいっぱいのスレッドプールを維持します。ただし、ほとんどの場合これらのスレッドは、ディスクからファイルをロードするときにI/Oイベントを待機している、クライアントが応答の一部を確認するまで待機している、またはデータベースを待機しているなど、何らかのリソースを待機しているため、アイドリング状態になります。クエリが完了しました。 NodeJSは、その後のイベントベースの非同期サーバー設計が非常にうまく機能することを驚異的に実証しました。明らかにJavaScriptはWebアプリケーションで使用される唯一の言語とはほど遠いため、他の言語(PythonおよびC#で注目))が非同期Webプログラミングを容易にする大きな動機もあります。

29
amon

オペレーティングシステムは プリエンプティブ スケジューリングを実行しなかったため、コルーチンは以前は有用でした。彼らがプリエンプティブスケジューリングの提供を開始すると、プログラムで定期的に制御を放棄する必要がなくなりました。

マルチコアプロセッサが普及すると、コルーチンを使用して タスクの並列処理 を達成し、システムの使用率を高く維持します(ある実行スレッドがリソースで待機する必要がある場合、別のスレッドがその場所で実行を開始できます) )。

NodeJSは、コルーチンがIOへの並列アクセスを得るために使用される特殊なケースです。つまり、IO要求を処理するために複数のスレッドが使用されますが、JavaScriptコードを実行するために単一のスレッドが使用されます。単一のスレッドでユーザーコードを実行する目的は、ミューテックスを使用します。これは、上記のようにシステムの使用率を高く維持しようとするカテゴリに分類されます。

7
dlasalle

初期のシステムでは、主にコルーチンを使用して同時実行性を提供していました。スレッドはオペレーティングシステムからのかなりの量のサポートを必要とします(ユーザーレベルでスレッドを実装できますが、システムが定期的にプロセスを中断するように調整するための何らかの方法が必要になります)。サポートがある場合でも、実装は困難です。 。

70年代か80年代までにすべての深刻なオペレーティングシステムがスレッドをサポートし(さらには90年代まではWindowsでさえ!)、スレッドはより一般的なものになったため、後でスレッドが引き継がれ始めました。そして、それらは使いやすいです。突然、誰もがスレッドが次の大きなものだと思った。

90年代後半までに亀裂が発生し始め、2000年代初頭にスレッドに深刻な問題があることが明らかになりました。

  1. 彼らは多くのリソースを消費します
  2. コンテキストスイッチは、比較的言えば、多くの時間がかかり、多くの場合不要です。
  3. 参照の局所性を破壊する
  4. 排他的アクセスを必要とする可能性のある複数のリソースを調整する正しいコードを書くことは予想外に難しい

時間の経過とともに、プログラムが常に実行する必要のあるタスクの数は急速に増加し、上記の(1)と(2)によって引き起こされる問題が増加しています。プロセッサ速度とメモリアクセス時間の差異が増大しており、問題がさらに悪化しています(3)。また、必要なリソースの数と種類に関するプログラムの複雑さが増し、問題の関連性が高まっています(4)。

しかし、少し一般性を失い、プログラマーがプロセスがどのように連携して動作するかを考える余計な負担をかけることで、コルーチンはこれらの問題をすべて解決できます。

  1. コルーチンは、スタック用の少数のページよりも賢明なリソースをほとんど必要とせず、スレッドのほとんどの実装よりもはるかに少ないです。
  2. コルーチンは、プログラマーが定義したポイントでのみコンテキストを切り替えます。また、通常、スレッドのように多くのコンテキスト情報(レジスタ値など)を保持する必要はありません。つまり、各スイッチは通常高速であり、必要な数も少なくなります。
  3. 生産者/消費者タイプの操作を含む一般的なコルーチンパターンは、局所性を積極的に増加させる方法でルーチン間でデータを渡します。さらに、コンテキストの切り替えは通常、作業ユニット間ではなく、つまり局所性が通常最小化されているときにのみ発生します。
  4. 操作の途中で任意に中断できないことをルーチンが知っている場合は、リソースのロックが必要になる可能性が低くなり、より単純な実装で正しく機能するようになります。
6
Jules

序文

まず、コルーチンが復活、並列処理されない理由を述べることから始めたいと思います。現代の実装はマルチプロセッシング機能を利用していないため、一般に、現代のコルーチンはタスクベースの並列処理を実現する手段ではありません。あなたがそれに最も近いものは fibres のようなものです。

現代の使用法(なぜ彼らが戻ってきたのか)

現代のコルーチンは、haskellなどの関数型言語で非常に便利な 遅延評価 を実現する方法として登場しました。この場合、セット全体を反復して操作を実行する代わりに、操作を実行できます。必要なだけの評価のみ(アイテムの無限セット、または早期終了とサブセットを備えた大規模セットの場合に役立ちます)。

Yieldキーワードを使用して、PythonやC#、コルーチンなどの言語で最新の generators (それ自体が遅延評価ニーズの一部を満たします)を作成します。実装は可能であっただけでなく、言語自体で特別な構文を使用しなくても可能でした(pythonは最終的にいくつかの助けとなるビットを追加しました)。コルーチンはfuture sその時点で変数の値が必要ない場合は、その値を明示的に要求するまで実際に取得を遅らせることができます(許可値を使用して、インスタンス化とは異なる時点でを遅延評価します)。

ただし、遅延評価を超えて、特にwebsphereでは、これらのcoルーチンは修正に役立ちます callback hell 。コルーチンは、データベースアクセス、オンライントランザクション、UIなどで役立ちます。クライアントマシン自体での処理時間が、必要なものへのアクセスを高速化することにはなりません。スレッディングは同じことを完全に満たすことができますが、この領域ではより多くのオーバーヘッドが必要であり、コルーチンとは対照的に、は実際にはタスクの並列処理に役立ちます

要するに、Web開発が成長し、関数型パラダイムが命令型言語とより融合するにつれて、コルーチンは非同期の問題と遅延評価の解決策となっています。コルーチンは、マルチプロセスのスレッド化とスレッド化が一般に不要、不便、または不可能である問題空間に到達します。

現代の例

JavaScript、Lua、C#、Pythonなどの言語のコルーチンはすべて、個々の関数によって実装を派生させて、メインスレッドの制御を他の関数に譲りますオペレーティングシステムコールとは関係ありません)。

this python example では、内部にawaitという名前の面白いpython関数があります。これは基本的にはyieldであり、これによりloopに実行が渡され、これにより別の関数(この場合は別のfactorial関数)を実行できるようになります。「タスクの並列実行"これは誤称です。実際には並列実行されていません。awaitキーワードを使用してinterleaving関数を実行します(覚えておくべきことは、特別なタイプの利回りです)

それらは、concurrentプロセスの制御の単一、非並列、yieldを許可しますが、task parallelではありません。 everを同時に操作しないでください。コルーチンは、最新の言語実装ではスレッドではありません。これらすべての言語のcoルーチンの実装は、これらの関数イールド呼び出し(プログラマーが実際に手動でcoルーチンに入れる必要がある)から派生しています。

編集:C++ Boost coroutine2は同じように機能し、これらの説明は、私がyeildsについて話している内容をよりよく視覚化する必要があります ここを参照 。ご覧のとおり、実装には「特別なケース」はありません。 boost fibres のようなものはルールの例外であり、その場合でも明示的な同期が必要です。

編集2:誰かが私がc#タスクベースのシステムについて話していると思ったので、私はそうではありませんでした。私は nityのシステム および単純 c#実装 について話していました

5
whn