読書補足:#
1. 基本概念:#
1.1 コルーチン:#
- “コルーチンは、非プリエンプティブなマルチタスクを生成するサブルーチンのコンピュータプログラムコンポーネントであり、コルーチンは異なるエントリーポイントでプログラムの実行を一時停止または開始することを許可します”。
- 技術的な観点から言えば、“コルーチンは実行を一時停止できる関数です”。
- それを “生成器のようなもの” と理解すれば、あなたは正しいです。
1.2 イベントループ:#
- イベントループは “イベントやメッセージの割り当てを待つプログラミングアーキテクチャです”。
- 基本的にイベントループは、“A が発生したときに B を実行する” というものです。
- この概念を説明する最も簡単な例は、すべてのブラウザに存在する JavaScript のイベントループです。
- 何かをクリックすると(“A が発生したとき”)、そのクリックアクションは JavaScript のイベントループに送信され、登録された onclick コールバックが存在するかどうかを確認します(“B を実行する”)。
- 登録されたコールバック関数があれば、クリックアクションの詳細情報とともに実行されます。
- イベントループは、イベントを収集し、それに対処する方法をループで発信するため、ループと見なされます。
1.3 Python のイベントループ:#
- Python にとって、イベントループを提供するために asyncio が標準ライブラリに追加されました。
- asyncio はネットワークサービスの問題を解決することに重点を置いており、ここでのイベントループはソケットからの I/O が読み取りおよび / または書き込みの準備ができていることを “A が発生したとき” として扱います(selectors モジュールを通じて)。
- GUI や I/O の他にも、イベントループは他のスレッドやサブプロセスでコードを実行するためにも頻繁に使用され、イベントループは調整メカニズムとして機能します(例えば、協調的マルチタスク)。
- もしあなたが Python の GIL を理解しているなら、イベントループは GIL を解放する必要がある場所で非常に便利です。
- イベントループは “A が発生したときに B を実行する” というループメカニズムを提供します。
- 基本的にイベントループは、何かが発生したときにそれをリスニングし、その事柄に関心を持ち、対応するコードを実行します。
- Python 3.4 以降、標準ライブラリ asyncio を通じてイベントループの機能が得られました。
1.4 async, await:#
- async/await を非同期プログラミングの API と見なす
- 基本的に async と await は魔法のような生成器を生成し、私たちはそれをコルーチンと呼びます。
- 同時に、awaitable オブジェクトや通常の生成器をコルーチンに変換するための追加のサポートが必要です。
- これらすべてが一緒になって並行性をサポートし、Python が非同期プログラミングをより良くサポートできるようにします。
- 同様の機能を持つスレッドと比較して、これはより巧妙でシンプルな方法です。
Python 3.4 では、非同期プログラミングのためにコルーチンとしてマークされた関数は次のようになります:
# これはPython 3.5でも動作します。
@asyncio.coroutine
def py34_coro():
yield from stuff()
Python 3.5 は types.coroutine デコレーターを追加し、生成器をコルーチンとしてマークすることができます。
async def を使用してコルーチン関数を定義できますが、この関数は yield 文のいかなる形式も含むことができません;return と await のみがコルーチンから値を返すことができます。
async def py35_coro():
await stuff()
あなたは async だけでなく、Python 3.5 は await 式も導入したことに気づくでしょう(これは async def 内でのみ使用できます)。
await の使用は yield from に非常に似ていますが、await が受け入れるオブジェクトは異なります。
await はもちろんコルーチンを受け入れることができますが、コルーチンの概念はすべての基礎です。
しかし、await を使用する場合、その受け入れるオブジェクトは awaitable オブジェクトでなければなりません:await() メソッドを定義し、そのメソッドはコルーチンではないイテレータを返さなければなりません。
コルーチン自体も awaitable オブジェクトと見なされます(これが collections.abc.Coroutine が collections.abc.Awaitable を継承する理由です)。
この定義は、Python が大部分の構文構造を底層でメソッド呼び出しに変換する伝統に従っています。a + b は実際には a.add(b) または b.radd(a) です。
なぜ async ベースのコルーチンと生成器ベースのコルーチンが対応する一時停止式で異なるのでしょうか?
主な理由は、Python のパフォーマンスを最適化するための考慮から、同じ API を持つ異なるオブジェクトを混同しないようにするためです。
生成器はデフォルトでコルーチンの API を実装しているため、コルーチンを使用したいときに生成器を誤って使用する可能性が非常に高いです。
すべての生成器がコルーチンベースの制御フローで使用できるわけではないため、生成器を誤って使用しないようにする必要があります。
async def を使用してコルーチンを定義できます。
コルーチンを定義する別の方法は、types.coroutine デコレーターを使用することです。
-- 技術的な実装の観点からは、CO_ITERABLE_COROUTINE マークが追加されます。
-- または collections.abc.Coroutine のサブクラスです。
生成器ベースの定義を使用してコルーチンの一時停止を実現することができます。
awaitable オブジェクトはコルーチンであるか、await() メソッドを定義したオブジェクトでなければなりません。
-- つまり collections.abc.Awaitable です。
-- そして__await__() はコルーチンではないイテレータを返さなければなりません。
await 式は基本的に yield from と同じですが、awaitable オブジェクトのみを受け入れます(通常のイテレータは不可です)。
async で定義された関数は return 文を含む必要があります。
-- すべての Python 関数のデフォルトの return None を含む。
-- および / または await 式(yield 式は不可です)。
async 関数の制限は、生成器ベースのコルーチンと通常の生成器を混合して使用しないようにするためです。なぜなら、これら二つの生成器に対する期待は非常に異なるからです。
1.5 Python のコルーチンと Golang の比較についての議論:#
- From Python to Go and Back Again
- PPT
- この PPT の見解について: go は pypy よりも性能が高くないが、複雑さとデバッグの難易度が大幅に増加する。
- 結論として rust を推奨。
- 非同期ライブラリの参考:
2. ソースコードモジュール:#
2.1 (futures.py)[./futures.py]#
2.1.1 参考:#
-
Python コルーチン:yield/send から async/await へ
- future ソースコードの解析
-
concurrent.futures ソースコードリーディングノート(Python)
- concurrent.futures は非同期ライブラリです。
- concurrent.futures — 非同期計算
2.1.2 生成器(Generator)VS イテレータ(iterator):#
-
improve-your-python-yield-and-generators-explained
- 訳文:あなたの Python を向上させる: ‘yield’と‘Generators(生成器)’の説明
- Python の外では、最も単純な生成器はコルーチン(coroutines)と呼ばれるものです。
- generator は一連の値を生成するために使用されます。
- yield は generator 関数の返り値のようなものです。
- yield が唯一行うもう一つのことは、generator 関数の状態を保存することです。
- generator は特別なタイプのイテレータ(iterator)です。
- イテレータと同様に、next () を使用して generator から次の値を取得できます。
- implicit に next () を呼び出していくつかの値を無視します。
-
- 生成器 (generator) は特別なイテレータであり、その実装はよりシンプルで優雅です。yield は生成器が__next__() メソッドを実装するための鍵です。