Lolik

not404

nothing
x
bilibili
github
telegram

非同期または同期

学習 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 つの主要なタイプがあります:coroutinetaskFuture

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

awaitcoroutineまたは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 開発などに適しています。

非同期にはいくつかの欠点があります:

  1. 複雑性が高く、非同期プログラミングにはコールバック関数、コルーチン、イベントループなどの概念と技術が必要であり、学習および使用コストが高いです。
  2. 非同期はエラーが発生しやすく、デバッグが困難です。非同期プログラミングの実行フローが比較的複雑であるため、同期プログラミングよりもデバッグが困難です。

非同期の難点:自分で書いたコードを制御できないことです。なぜなら、実行順序が予測できないからです。それは 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

読み込み中...
文章は、創作者によって署名され、ブロックチェーンに安全に保存されています。