学習 python のウェブフレームワーク fastapi を見て、さまざまな利点があります。「非同期サポート、パフォーマンスが良く、最速の Python ウェブフレームワークの一つです」
最も簡単な FastAPI は以下のようになります:
from fastapi import FastAPI
app = FastAPI()
@app.get("/")
async def root():
return {"message": "Hello World"}
async def
キーワードに注目してください。非同期の Python ライブラリを使用したことがある場合、呼び出し時にawait
キーワードを前に付ける必要があることを知っているかもしれません。しかし、それを追加するとエディタが非同期関数内で使用するようにと警告します。そのため、def
の前にasync
を追加し、await
の後ろの関数上にマウスを置くと、戻り値がcoroutine
のものであることがわかります。a()
のようにこの非同期関数を直接呼び出すとエラーが発生し、asyncio.run()
を使用して実行する必要があります。
非同期のコードは、プログラムが特定の時点で「待機」することを示すことができます(I/O 操作、ネットワークなど)。CPU を占有する必要はなく、制御を他のタスクに委ね、実際の結果が完了したときにプログラムに通知することができます。これにより、プログラム内の他のタスクがこの遅いタスクを待つ必要はありません。つまり、同期は順番に実行され、非同期は異なるタスク間で切り替えられます。
Python では、await a()
はプログラムに a () の実行を待つ必要がないことを示します。a () 自体または内部のある時点で一時停止でき、将来のある時点で結果が返されることを示します。Python 公式ドキュメントでは、a () を「awaitable オブジェクト」と呼んでいます(https://docs.python.org/zh-cn/3/library/asyncio-task.html#id3)。
もしオブジェクトが
await
ステートメントで使用できる場合、それは awaitable オブジェクトです。
awaitable オブジェクトには 3 つの主要なタイプがあります:coroutine、task、Future。Future は非同期操作の最終結果を表します。スレッドセーフではありません。
つまり、await
の後には coroutine(コルーチン)、task(タスク)、Future が来ることができます。
連続してawait
を使用すると、同期されていることがわかります。なぜなら、await の後に coroutine がある場合、まずそれを task に変換し、asyncio.create_task()
も同じことをします。await は 2 つのことを同時に行うため、連続してawait
タスクが同時に作成されないため、asyncio.gather()
を使用してタスクを並行して実行します。
import asyncio
import time
async def f1():
print(f'f1--start-{time.time()}')
await asyncio.sleep(1)
print(f'f1--end-{time.time()}')
async def f2():
print(f'f2--start-{time.time()}')
await asyncio.sleep(1)
print(f'f2--end-{time.time()}')
async def main():
await f1()
await f2()
if __name__ == '__main__':
asyncio.run(main())
f1--start-1681478831.0485213
f1--end-1681478832.0549097
f2--start-1681478832.0549097
f2--end-1681478833.0619018
main 関数を次のように変更します:
async def main():
await asyncio.gather(f1(), f2())
f1--start-1681478900.615097
f2--start-1681478900.615097
f1--end-1681478901.6200216
f2--end-1681478901.6200216
または、次のようにタスクを同時に作成します。
async def main():
task1 = asyncio.create_task(f1())
task2 = asyncio.create_task(f2())
await task1
await task2
#または await asyncio.gather(task1, task2)
f1--start-1681479408.9956422
f2--start-1681479408.9956422
f1--end-1681479410.0039244
f2--end-1681479410.0039244
await
はcoroutine
またはtask
を受け取ることができます。coroutine
の場合、それを task に変換します。直接連続して await を使用する場合、f1 を呼び出すとすぐに実行が開始されますが、実行中に制御がすぐに main () 関数に戻り、main () 関数は f1 の実行が完了するのを待ってから次の行のコードを実行します。しかし、f1 の実行が完了するのを待っている間、f2 は呼び出されません。
2 つのタスクを作成するだけで、await
を追加しない場合、main () 関数はタスク 1 とタスク 2 を作成した後、すぐに戻ります。これら 2 つのタスクの完了を待ちません。つまり、f1-start と f2-start のみが表示され、end は表示されません。
非同期の利点は次のとおりです:1 つのスレッドで複数のタスクを処理できるため、各タスクに新しいスレッドを作成する必要がありません。スレッド切り替えのオーバーヘッドを削減し、プログラムの並行性を向上させることができます。I/O 操作を待っている間に他のタスクを処理できるため、プログラムの実行をブロックしません。
非同期の意義は、CPU を最大限に活用し、プログラムの効率を向上させることです。そのため、計算集中型のプログラムでは非同期の意義はほとんどありません。むしろ、大量の I/O 操作を処理する場面、例えばネットワークプログラミング、Web 開発などに適しています。
非同期にはいくつかの欠点があります:
- 複雑性が高く、非同期プログラミングにはコールバック関数、コルーチン、イベントループなどの概念と技術が必要であり、学習および使用コストが高いです。
- 非同期はエラーが発生しやすく、デバッグが困難です。非同期プログラミングの実行フローが比較的複雑であるため、同期プログラミングよりもデバッグが困難です。
非同期の難点:自分で書いたコードを制御できないことです。なぜなら、実行順序が予測できないからです。それは CPU 時間を圧搾し、CPU がアイドルしないようにします。
時間は海綿のようなもので、あなたが絞る限り、いつも何かが残ります。- 魯迅
参考:
https://fastapi.tiangolo.com/zh/async/#is-concurrency-better-than-parallelism
https://docs.python.org/zh-cn/3/library/asyncio-task.html#coroutines