読書補足:#
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 デコレーターを追加し、asyncio.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)と呼ばれるものです。
- ジェネレーターは一連の値を生成するために使用されます。
- yield はジェネレーター関数の戻り値のようなものです。
- yield が唯一行う別のことは、ジェネレーター関数の状態を保存することです。
- ジェネレーターは特別なタイプのイテレーター(iterator)です。
- イテレーターと同様に、next () を使用してジェネレーターから次の値を取得できます。
- implicit に next () を呼び出していくつかの値を無視します。
-
- ジェネレーター(generator)は特別なイテレーターであり、その実装はよりシンプルで優雅です。yield はジェネレーターが__next__() メソッドを実装するための鍵です。