ホームページ > バックエンド開発 > Python チュートリアル > BlockBuster の紹介: asyncio イベント ループはブロックされていますか?

BlockBuster の紹介: asyncio イベント ループはブロックされていますか?

Patricia Arquette
リリース: 2025-01-09 06:29:43
オリジナル
856 人が閲覧しました

Introducing BlockBuster: is my asyncio event loop blocked?

Python 3.5 では、同時実行性を処理するスレッドの代替として非同期 I/O が導入されました。非同期 I/O と Python での asyncio 実装の利点は、メモリを大量に消費するオペレーティング システム スレッドを生成しないため、システムの使用リソースが減り、スケーラビリティが向上することです。さらに、asyncio では、スケジュール ポイントは await 構文によって明確に定義されますが、スレッドベースの同時実行では、GIL が予測できないコード ポイントで解放される可能性があります。その結果、asyncio ベースの同時実行システムは理解とデバッグが容易になります。最後に、asyncio タスクをキャンセルできますが、これはスレッドを使用する場合は簡単ではありません。

ただし、これらの利点を真に活用するには、非同期コルーチンでの呼び出しのブロックを避けることが重要です。ブロック呼び出しには、ネットワーク呼び出し、ファイル システム呼び出し、sleep 呼び出しなどがあります。 asyncio は内部でシングルスレッドのイベント ループを使用してコルーチンを同時に実行するため、これらのブロック呼び出しは有害です。したがって、コルーチン内でブロッキング呼び出しを行うと、イベント ループ全体とすべてのコルーチンがブロックされ、アプリケーションの全体的なパフォーマンスに影響します。

次に、コードの同時実行を防ぐブロック呼び出しの例を示します。

<code class="language-python">import asyncio
import datetime
import time

async def example(name):
    print(f"{datetime.datetime.now()}: {name} start")
    time.sleep(1)  # time.sleep 是一个阻塞函数
    print(f"{datetime.datetime.now()}: {name} stop")

async def main():
    await asyncio.gather(example("1"), example("2"))

asyncio.run(main())</code>
ログイン後にコピー
ログイン後にコピー

実行結果は次のようになります:

<code>2025-01-07 18:50:15.327677: 1 start
2025-01-07 18:50:16.328330: 1 stop
2025-01-07 18:50:16.328404: 2 start
2025-01-07 18:50:17.333159: 2 stop</code>
ログイン後にコピー
ログイン後にコピー

ご覧のとおり、2 つのコルーチンは同時に実行されていません。

この問題を解決するには、非ブロッキング同等のものを使用するか、実行をスレッド プールに延期する必要があります。

<code class="language-python">import asyncio
import datetime
import time

async def example(name):
    print(f"{datetime.datetime.now()}: {name} start")
    await asyncio.sleep(1)  # 将阻塞的 time.sleep 调用替换为非阻塞的 asyncio.sleep 协程
    print(f"{datetime.datetime.now()}: {name} stop")

async def main():
    await asyncio.gather(example("1"), example("2"))

asyncio.run(main())</code>
ログイン後にコピー
ログイン後にコピー

実行結果は次のようになります:

<code>2025-01-07 18:53:53.579738: 1 start
2025-01-07 18:53:53.579797: 2 start
2025-01-07 18:53:54.580463: 1 stop
2025-01-07 18:53:54.580572: 2 stop</code>
ログイン後にコピー

ここでは 2 つのコルーチンが同時に実行されます。

問題は、メソッドがブロックしているかどうかを特定するのが必ずしも簡単ではないことです。特にコードベースが大きい場合やサードパーティのライブラリを使用している場合はそうです。場合によっては、コードの深い部分でブロッキング呼び出しが行われることがあります。

たとえば、このコードはブロックしますか?

<code class="language-python">import blockbuster
from importlib.metadata import version

async def get_version():
    return version("blockbuster")</code>
ログイン後にコピー

Python は起動時にパッケージのメタデータをメモリにロードしますか? blockbuster モジュールがロードされたときに完了しますか?それとも version() を呼び出すときでしょうか?結果はキャッシュされますか? 後続の呼び出しはノンブロッキングになりますか?正しい答えは、version() を呼び出すときに行われます。これには、インストールされたパッケージの METADATA ファイルの読み取りが含まれます。そして結果はキャッシュされません。したがって、version() はブロック呼び出しであり、常にスレッドに延期される必要があります。 importlib のコードを詳しく調べずにこの事実を知るのは困難です。

ブロッキング コールを検出する 1 つの方法は、asyncio のデバッグ モードをアクティブにして、時間がかかりすぎるブロッキング コールをログに記録することです。ただし、これは最も効率的なアプローチではありません。トリガー タイムアウトよりも短いブロック時間が多くてもパフォーマンスに悪影響を及ぼし、テスト/開発でのブロック時間は運用環境とは異なる可能性があるためです。たとえば、データベースが大量のデータをフェッチする必要がある場合、運用環境ではデータベースの呼び出しに時間がかかることがあります。

ここでブロックバスターの出番です! BlockBuster をアクティブにすると、asyncio イベント ループから呼び出された場合にエラーをスローするいくつかのブロッキング Python フレームワーク メソッドにパッチが適用されます。デフォルトのパッチ適用メソッドには、osiotimesocket、および sqlite モジュールのメソッドが含まれます。 BlockBuster によって検出されたメソッドの完全なリストについては、プロジェクトの Readme を参照してください。その後、単体テスト モードまたは開発モードで BlockBuster をアクティブにして、ブロックされている呼び出しを捕捉して修正できます。 JVM 用の素晴らしい BlockHound ライブラリをご存知の場合は、Python でも同じ原理です。クリエイターのおかげで、BlockHound は BlockBuster の素晴らしいインスピレーションの源でした。

上記のブロック コード スニペットで BlockBuster を使用する方法を見てみましょう。

まず、blockbuster パッケージをインストールする必要があります

<code class="language-python">import asyncio
import datetime
import time

async def example(name):
    print(f"{datetime.datetime.now()}: {name} start")
    time.sleep(1)  # time.sleep 是一个阻塞函数
    print(f"{datetime.datetime.now()}: {name} stop")

async def main():
    await asyncio.gather(example("1"), example("2"))

asyncio.run(main())</code>
ログイン後にコピー
ログイン後にコピー

その後、pytest フィクスチャと blockbuster_ctx() メソッドを使用して、各テストの開始時に BlockBuster をアクティブ化し、ティアダウン中に非アクティブ化することができます。

<code>2025-01-07 18:50:15.327677: 1 start
2025-01-07 18:50:16.328330: 1 stop
2025-01-07 18:50:16.328404: 2 start
2025-01-07 18:50:17.333159: 2 stop</code>
ログイン後にコピー
ログイン後にコピー

これを pytest で実行すると、次の結果が得られます

<code class="language-python">import asyncio
import datetime
import time

async def example(name):
    print(f"{datetime.datetime.now()}: {name} start")
    await asyncio.sleep(1)  # 将阻塞的 time.sleep 调用替换为非阻塞的 asyncio.sleep 协程
    print(f"{datetime.datetime.now()}: {name} stop")

async def main():
    await asyncio.gather(example("1"), example("2"))

asyncio.run(main())</code>
ログイン後にコピー
ログイン後にコピー

: 通常、実際のプロジェクトでは、blockbuster() フィクスチャは conftest.py ファイルでセットアップされます。

結論

BlockBuster は asyncio プロジェクトで非常に役立つと思います。これは、私が取り組んだプロジェクトで多くのブロッキング コールの問題を検出するのに役立ちました。しかし、それは万能薬ではありません。特に、一部のサードパーティ ライブラリは、ネットワークまたはファイル システムと対話するために Python フレームワーク メソッドを使用せず、代わりに C ライブラリをラップします。これらのライブラリについては、テスト セットアップにルールを追加して、これらのライブラリへの呼び出しのブロックをトリガーできます。 BlockBuster はオープン ソースでもあります。コア プロジェクトにお気に入りのライブラリのルールを追加するための貢献は大歓迎です。問題や改善の余地がある場合は、プロジェクトの問題トラッカーでフィードバックをお待ちしています。

いくつかのリンク:

  • GitHub プロジェクト
  • 質問
  • Pypi のパッケージ

以上がBlockBuster の紹介: asyncio イベント ループはブロックされていますか?の詳細内容です。詳細については、PHP 中国語 Web サイトの他の関連記事を参照してください。

ソース:php.cn
このウェブサイトの声明
この記事の内容はネチズンが自主的に寄稿したものであり、著作権は原著者に帰属します。このサイトは、それに相当する法的責任を負いません。盗作または侵害の疑いのあるコンテンツを見つけた場合は、admin@php.cn までご連絡ください。
著者別の最新記事
人気のチュートリアル
詳細>
最新のダウンロード
詳細>
ウェブエフェクト
公式サイト
サイト素材
フロントエンドテンプレート