コルーチンが戻ってきたのはなぜですか?[閉まっている]


19

コルーチンの基礎の大部分は60年代/​​ 70年代に発生し、その後、代替手段(スレッドなど)を支持して停止しました。

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



9
彼らが去ったかどうかはわかりません。
Blrfl

回答:


26

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

80年代、90年代、そして今日の最大のプログラミングパラダイムの1つはOOPです。OOPの歴史、特にSimula言語の開発を見ると、クラスがコルーチンから進化したことがわかります。Simulaは、離散イベントを含むシステムのシミュレーションを目的としていました。システムの各要素は、1つのシミュレーションステップの期間中にイベントに応じて実行され、他のプロセスに作業を任せるための処理を行う個別のプロセスでした。Simula 67の開発中に、クラスコンセプトが導入されました。これで、コルーチンの永続的な状態がオブジェクトメンバーに保存され、メソッドを呼び出すことでイベントがトリガーされます。詳細については、Nygaard&Dahl の論文「SIMULA言語の開発」を読んでください。

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

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

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

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

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

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

    [1]:技術的には、これらの制限はPythonリファレンス実装であるCPythonにのみ適用されます。Jythonのような他の実装では、並列に実行できる実際のスレッドを提供していますが、同等の動作を実装するにはかなりの時間をかけなければなりません。基本的に、すべての変数またはオブジェクトメンバは揮発性変数であるため、すべての変更はアトミックであり、すべてのスレッドですぐに表示されます。もちろん、揮発性変数を使用することは、通常の変数を使用するよりもはるかに高価です。

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

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

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

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

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

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

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


盗作のリスクを回避するために、最後から4番目の段落を調達することをお勧めします。これは、私が読んだ別の情報源とほぼ同じです。さらに、スレッドよりも桁違いに小さなオーバーヘッドがありますが、コルーチンのパフォーマンスを「間接的な関数呼び出し」に単純化することはできません。コルーチンインプリメンテーションの詳細については、こちらこちらをご覧ください
WHN

1
@snb提案された編集に関して:GILはCPython実装の詳細かもしれませんが、基本的な問題は、Python 言語にはデータの並列突然変異を指定する明示的なメモリモデルがないことです。GILは、これらの問題を回避するためのハックです。しかし、真の並列性を備えたPython実装は、たとえばJython bookで説明されているように、同等のセマンティクスを提供するために非常に長い時間をかけなければなりません。基本的に、すべての変数またはオブジェクトフィールドは、高価な揮発性変数でなければなりません。
アモン

3
@snb盗作について:盗作は、特に学術的な文脈において、あなた自身の考えとして誤って提示しています。それは深刻な申し立てですが、あなたはそのように言ったのではないでしょう。「スレッドは基本的にプロセスに似ています」という段落は、オペレーティングシステムに関する講義や教科書で教えられているように、よく知られている事実を繰り返し述べているだけです。これらの事実を簡潔に表現する方法は非常に多くあるため、おなじみの段落の音が聞こえても驚かない。
アモン

私は、Pythonのことを意味する意味を変更していないでしたメモリモデルを持っています。また、volatileを使用してもパフォーマンスが低下することはありませんvolatile volatileは、現在のコンテキストで明示的な操作を行わなくても変数が変更されないと想定できる方法でコンパイラが変数を最適化できないことを意味します。Jythonの世界では、VM JITコンパイルを使用するため、これは実際に重要かもしれませんが、CPythonの世界では、JITの最適化について心配することはありません。 。
WHN

7

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

マルチコアプロセッサが普及するにつれて、コルーチンを使用してタスクの並列性を実現したり、システムの使用率を高くしたりします(1つの実行スレッドがリソースで待機する必要がある場合、別のスレッドが代わりに実行を開始できます)。

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


4
ただし、コルーチンはOS管理ではありません。OSは、C ++ファイバーとは異なり、コルーチンが何であるかを知りません
過剰交換

多くのOSにはコルーチンがあります。
ヨルグWミットタグ

PythonやJavascript ES6 +などのコルーチンはマルチプロセスではありませんか?それらはどのようにタスクの並列性を達成しますか?
-whn

1
@Mael最近のコルーチンの「リバイバル」はpythonとjavascriptに由来しますが、どちらも私が理解しているようにコルーチンとの並列性を達成していません。つまり、タスクの並列化がコルーチンがまったく「戻る」理由ではないため、この答えは間違っているということです。 また、Luasもマルチプロセスではありませんか?編集:並列処理について話しているのではないことに気付いたのですが、そもそもなぜ返事をくれたのですか?明らかに彼らはこれについて間違っているので、dlasalleに返信してください。
-whn

3
@dlasalleいいえ、「並行して実行している」とは言っても、コードが物理的に同時に実行されるわけではありません。GILはそれを停止し、非同期はCPythonのマルチプロセッシングに必要な別のプロセス(別のGIL)を生成しません。非同期は、単一スレッドのイールドで機能します。彼らは、彼らが実際に平均いくつかの機能は他の機能の仕事にyeildingと「parralelを」と言うときinterleving機能の実行を。implのため、Python非同期プロセスを並行して実行することはできません。私は現在、並列コルーチン、Lua、Javascript、Pythonを実行しない3つの言語を持っています。
-whn

5

初期のシステムでは、コルーチンを使用して並行性を提供していました。主な理由は、それが最も簡単な方法だからです。スレッドには、オペレーティングシステムからのかなりの量のサポートが必要です(ユーザーレベルで実装できますが、システムが定期的にプロセスを中断するための何らかの方法が必要になります)。 。

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

90年代後半までにクラックが発生し始め、2000年代初頭にはスレッドに重大な問題があることが明らかになりました。

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

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

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

  1. コルーチンは、スタック用の少数のページよりも賢明なリソースをほとんど必要とせず、スレッドのほとんどの実装よりはるかに少ない。
  2. コルーチンは、プログラマーが定義したポイントでのみコンテキストを切り替えます。これは、必要な場合にのみ意味します。また、通常、スレッドほど多くのコンテキスト情報(レジスタ値など)を保持する必要はありません。つまり、各スイッチは通常、より高速であり、必要な情報も少なくなります。
  3. プロデューサー/コンシューマータイプの操作を含む一般的なコルーチンパターンは、ローカリティを積極的に高める方法でルーチン間でデータを引き渡します。さらに、コンテキスト切り替えは、通常、作業単位内ではなく、作業単位間でのみ発生します。つまり、とにかくローカリティが通常最小化される時点です。
  4. ルーチンが操作の途中で勝手に割り込むことができないことを知っている場合、リソースのロックは必要でなくなり、より単純な実装が正しく機能するようになります。

5

序文

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

モダンな使用法(なぜ戻ってきたのか)

怠zyな評価を達成する方法として、現代のコルーチンが登場しました、。haskellのような関数型言語では非常に便利です。セット全体を反復して操作を実行する代わりに、必要なだけ評価のみの操作を実行できます(アイテムの無限のセットや、早期終了とサブセットを持つその他の大きなセットに便利です。

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

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

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

現代の例

Javascript、Lua、C#、Pythonなどの言語のコルーチンはすべて、個々の関数があきらめることによって実装を派生させます他の機能(オペレーティングシステムコールとは何の関係)にメインスレッドの制御を。

では、このPythonの例では、我々はと呼ばれるもので面白いPython関数持ってawait、それの内側を。これは基本的にyieldであり、これにより実行が行われ、loop異なる関数(この場合は異なるfactorial関数)の実行が許可されます。「タスクの並列実行」という誤った表記がある場合、実際には並列で実行されておらず、awaitを使用して関数のインターリーブを実行していることに注意してください。キーワードのあることに注意してください(これは単なる特殊なタイプのyieldです)

彼らはのための単一の制御、非パラレル、利回り許可同時ではないプロセス、タスク並列これらのタスクが動作しないという意味で、これまでと同じ時間にします。コルーチンは、現代の言語実装でスレッドではありません。coルーチンのこれらすべての言語実装は、これらの関数yield呼び出しから派生します(プログラマーは実際にcoルーチンに手動で入力する必要があります)。

編集:C ++ Boost coroutine2は同じように機能し、その説明により、私がyeildsで話していることのより良い視覚が得られるはずです。こちらを参照してください。ご覧のとおり、実装には「特殊なケース」はありません。ファイバーのブーストなどなどはルールの例外であり、明示的な同期が必要です。

EDIT2:誰かが私がC#タスクベースのシステムについて話していると思ったので、私はそうではありませんでした。私はUnityのシステムと素朴なC#の実装について話していました


@ T.Sar C#には「自然な」コルーチンがあり、C ++(変更される可能性もあります)もpythonもありません(まだありました)。しかし、コルーチンのすべてのC#実装(ユニティの実装など)は、私が説明したとおり、歩留まりに基づいています。また、ここでの「ハック」の使用は無意味です。すべてのプログラムがハックであるのは、言語で常に定義されているわけではないからです。私はC#の「タスクベースのシステム」を何かと混同することは決してありません。それについては言及しませんでした。
-whn

あなたの答えをもう少し明確にすることをお勧めします。C#にはawait命令の概念とタスクベースの並列化システムの両方があります-C#とそれらの単語を使用して、Pythonが実際に並列ではない方法についてpythonで例を与えると、多くの困難な混乱を引き起こす可能性があります。また、最初の文を削除します-そのような答えで他のユーザーを直接攻撃する必要はありません。
T.サー-モニカーの復活
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.