パフォーマンスのボトルネックを突破する: Asyncio を使用して同時実行性の高い Python アプリケーションを構築する

並行プログラミングは、複数のタスクを同時に実行するプログラミング手法であり、Python ではasyncio非同期プログラミングの強力なツールです。asyncioコルーチンの概念に基づいて、I/O 集中型のタスクを効率的に処理できます。この記事ではasyncioその基本原理と活用方法を紹介します。

なぜ必要なのですかasyncio

I/O 操作を処理する場合、マルチスレッドを使用すると、通常のシングル スレッドと比較して効率が大幅に向上することがわかっています。この場合、なぜ Asyncio が必要なのでしょうか?

マルチスレッドには多くの利点があり、広く使用されていますが、次のような制限もあります。

  • たとえば、マルチスレッドの実行プロセスは中断されやすいため、競合状態が発生する可能性があります。
  • 別の例として、スレッドの切り替え自体に一定のロスがあり、スレッド数を無制限に増やすことはできないため、I/O 操作が非常に重い場合、マルチスレッドでは高効率と高効率の要件を満たせない可能性があります。品質。

これらの問題を解決するために Asyncio が誕生しました。

同期 VS 非同期

まず、Sync (同期) と Async (非同期) の概念を区別しましょう。

  • いわゆる同期とは、操作が次々に実行されることを意味します。次の操作は、前の操作が完了するまで待ってから実行する必要があります。
  • 非同期とは、異なる操作を交互に実行できることを意味し、操作の 1 つがブロックされても、プログラムは待機せず、実行可能な操作を見つけて実行を継続します。

非同期の仕組み

  1. コルーチン:Asyncioコルーチンを使用して非同期操作を実装します。コルーチンは、asyncキーワードを使用して定義される特別な種類の関数です。コルーチンでは、awaitキーワードを使用して現在のコルーチンの実行を一時停止し、非同期操作が完了するのを待つことができます。
  2. イベント ループ (イベント ループ): イベント ループは、 のAsyncioコア メカニズムの 1 つです。コルーチンのスケジュールと実行を担当し、コルーチン間の切り替えを処理します。イベント ループは実行可能なタスクのポーリングを継続し、タスクの準備が完了すると (IO の完了やタイマーの期限切れなど)、イベント ループはそのタスクを実行キューに入れて次のタスクに進みます。
  3. 非同期タスク: ではAsyncio、非同期タスクを作成することでコルーチンを実行します。非同期タスクはasyncio.create_task()関数によって作成され、コルーチンを待機可能なオブジェクトにカプセル化し、それを処理のためにイベント ループに送信します。
  4. 非同期 IO 操作:Asyncioコルーチンやイベント ループを通じてシームレスに統合できる、一連の非同期 IO 操作 (ネットワーク リクエスト、ファイルの読み取りと書き込みなど) を提供します。非同期 IO 操作を使用すると、IO の完了を待機している間のブロックを回避し、プログラムのパフォーマンスと同時実行性を向上させることができます。
  5. コールバック:Asyncio非同期操作の結果を処理するためのコールバック関数もサポートされています。コールバック関数は、関数を使用してasyncio.ensure_future()待機可能なオブジェクトとしてカプセル化し、処理のためにイベント ループに送信できます。
  6. 同時実行:Asyncio複数のコルーチン タスクを同時に実行できます。イベント ループは、タスクの準備状況に応じてコルーチンの実行を自動的にスケジュールし、効率的な同時プログラミングを実現します。

要約すると、Asyncio動作原理はコルーチンとイベント ループのメカニズムに基づいています。非同期操作にコルーチンを使用し、イベント ループがコルーチンのスケジューリングと実行を担当することにより、Asyncio効率的な非同期プログラミング モデルが実現します。

コルーチンと非同期プログラミング

コルーチンは のasyncio重要な概念であり、スレッド切り替えのオーバーヘッドなしでタスク間を素早く切り替えることができる軽量の実行ユニットです。コルーチンはキーワードによって定義できasyncawaitキーワードはコルーチンの実行を一時停止し、操作が完了するのを待ってから続行するために使用されます。

以下は、非同期プログラミングにコルーチンを使用する方法を示す簡単なサンプル コードです。

import asyncio
​
async def hello():
    print("Hello")
    await asyncio.sleep(1)  # 模拟耗时操作
    print("World")
​
# 创建一个事件循环
loop = asyncio.get_event_loop()
​
# 将协程加入事件循环并执行
loop.run_until_complete(hello())

この例では、関数はキーワードによって定義されたhello()コルーチンですasyncコルーチン内では、awaitコルーチンの実行を一時停止するために使用できます。ここでは、asyncio.sleep(1)時間のかかる操作をシミュレートするために使用します。このメソッドを通じてrun_until_complete()、コルーチンがイベント ループに追加され、実行されます。

非同期 I/O 操作

asyncioこれは主に、ネットワーク要求、ファイルの読み取りと書き込み、その他の操作など、I/O 集中型のタスクを処理するために使用されます。一連の非同期 I/O 操作 API が提供されており、awaitキーワードと組み合わせて使用​​すると、非同期プログラミングを簡単に実装できます。

asyncio以下は、以下を使用して非同期ネットワーク リクエストを作成する方法を示す簡単なサンプル コードです。

import asyncio
import aiohttp
​
async def fetch(session, url):
    async with session.get(url) as response:
        return await response.text()
​
async def main():
    async with aiohttp.ClientSession() as session:
        html = await fetch(session, 'https://www.example.com')
        print(html)
​
# 创建一个事件循环
loop = asyncio.get_event_loop()
​
# 将协程加入事件循环并执行
loop.run_until_complete(main())

この例では、aiohttpライブラリを使用してネットワーク要求を行います。関数は、メソッドを通じて非同期 GET リクエストを開始し、キーワードを通じて返される応答を待つfetch()コルーチンです。この関数は別のコルーチンであり、内部で再利用用のオブジェクトを作成し、メソッドを呼び出して Web ページのコンテンツを取得して印刷します。session.get()awaitmain()ClientSessionfetch()

知らせ:

aiohttpライブラリの代わりにライブラリを使用していることからrequests、その理由は、request ライブラリは Asyncio と互換性がないものの、aiohttp ライブラリは互換性があるためです。

Asyncio、特にその強力な機能を有効に活用したい場合は、多くの場合、対応する Python ライブラリのサポートが必要です。

複数のタスクを同時に実行する

asyncioasyncio.gather()また、やasyncio.wait()など、複数のタスクを同時に実行するためのメカニズムも提供します。これらのメカニズムを使用して複数のコルーチン タスクを同時に実行する方法を示すサンプル コードを次に示します。

import asyncio
​
async def task1():
    print("Task 1 started")
    await asyncio.sleep(1)
    print("Task 1 finished")
​
async def task2():
    print("Task 2 started")
    await asyncio.sleep(2)
    print("Task 2 finished")
​
async def main():
    await asyncio.gather(task1(), task2())
​
# 创建一个事件循环
loop = asyncio.get_event_loop()
​
# 将协程加入事件循环并执行
loop.run_until_complete(main())

task1()この例では、2 つのコルーチン タスクとを定義しますtask2()。どちらも時間のかかる操作を実行します。コルーチンは、両方のタスクを同時に開始し、完了するのを待つことmain()によって機能します。asyncio.gather()同時実行によりプログラムの実行効率を向上させることができます。

どのように選ぶか?

実際のプロジェクトでは、マルチスレッドまたはasyncio?を選択します。偉い人がこのように要約しました、とても鮮やかです

if io_bound:
    if io_slow:
        print('Use Asyncio')
    else:
        print('Use multi-threading')
else if cpu_bound:
    print('Use multi-processing')
  • I/O バウンドであり、I/O 操作が非常に遅く、多くのタスク/スレッドの連携が必要な場合は、Asyncio を使用する方が適切です。
  • I/O バウンドであるが、I/O 操作が高速で、限られた数のタスク/スレッドしか必要としない場合は、マルチスレッドを使用しても問題ありません。
  • CPU に依存している場合は、プログラムの効率を向上させるために複数のプロセスを使用する必要があります。

戦闘

リストを入力し、リスト内の各要素について、0 からこの要素までのすべての整数の二乗和を計算したいとします。

同期実装

import time
​
def cpu_bound(number):
    return sum(i * i for i in range(number))
​
​
def calculate_sums(numbers):
    for number in numbers:
        cpu_bound(number)
​
​
def main():
    start_time = time.perf_counter()
    numbers = [10000000 + x for x in range(20)]
    calculate_sums(numbers)
    end_time = time.perf_counter()
    print('Calculation takes {} seconds'.format(end_time - start_time))
​
​
if __name__ == '__main__':
    main()

所要実行時間Calculation takes 17.976343413000002 seconds

非同期実装

concurrent.futures成し遂げる

import time
from concurrent.futures import ThreadPoolExecutor, ProcessPoolExecutor, as_completed
​
def cpu_bound(number):
    return sum(i * i for i in range(number))
​
​
def calculate_sums(numbers):
    with ProcessPoolExecutor() as executor:
        results = executor.map(cpu_bound, numbers)
        results = [result for result in results]
    print(results)
​
def main():
    start_time = time.perf_counter()
    numbers = [10000000 + x for x in range(20)]
    calculate_sums(numbers)
    end_time = time.perf_counter()
    print('Calculation takes {} seconds'.format(end_time - start_time))
​
​
if __name__ == '__main__':
    main()

必要な実行時間Calculation takes 7.314132894999999 seconds

この改良されたコードでは、 を使用してconcurrent.futures.ProcessPoolExecutorプロセス プールを作成し、executor.map()メソッドを使用してタスクを送信し、結果を取得します。

executor.map()を使用した後、結果を取得する必要がある場合は、結果をリストに反復処理するか、他のメソッドを使用して結果を処理できることに注意してください

multiprocessing成し遂げる

import time
import multiprocessing
​
​
def cpu_bound(number):
    return sum(i * i for i in range(number))
​
​
def calculate_sums(numbers):
    with multiprocessing.Pool() as pool:
        pool.map(cpu_bound, numbers)
​
​
def main():
    start_time = time.perf_counter()
    numbers = [10000000 + x for x in range(20)]
    calculate_sums(numbers)
    end_time = time.perf_counter()
    print('Calculation takes {} seconds'.format(end_time - start_time))
​
​
if __name__ == '__main__':
    main()

実行時間Calculation takes 6.051121667 seconds

concurrent.futures.ProcessPoolExecutorと はmultiprocessingどちらも Python でマルチプロセスの同時実行を実現するために使用されるライブラリであり、いくつかの違いがあります。

  1. インターフェイスベースのカプセル化:モジュールによって提供される高レベルのインターフェイスであり、基礎となるマルチプロセス関数をカプセル化し、マルチプロセス コードの作成を容易にしますconcurrent.futures.ProcessPoolExecutorconcurrent.futuresこれmultiprocessingは Python の標準ライブラリの 1 つであり、完全なマルチプロセスのサポートを提供し、プロセスの直接操作を可能にします。
  2. API の使用方法:concurrent.futures.ProcessPoolExecutor使用方法はスレッド プールに似ており、呼び出し可能なオブジェクト (関数など) を実行のためにプロセス プールに送信し、Future実行結果を取得するために使用できるオブジェクトを返します。これはmultiprocessing、プロセスを明示的に作成、開始、制御し、複数のプロセス間でキューまたはパイプを使用して通信できる、下位レベルのプロセス管理および通信インターフェイスを提供します。
  3. スケーラビリティと柔軟性:multiprocessing下位レベルのインターフェイスを提供するため、concurrent.futures.ProcessPoolExecutorよりも柔軟性が高くなります。プロセスを直接操作することで、プロセスの優先順位の設定やプロセス間のデータ共有など、プロセスごとにきめ細かい制御が可能になります。単純なタスクの並列化により適していますがconcurrent.futures.ProcessPoolExecutor、多くの低レベルの詳細が隠蔽されるため、マルチプロセス コードの作成が容易になり、使いやすくなります。
  4. クロスプラットフォームのサポート:concurrent.futures.ProcessPoolExecutorどちらもmultiprocessingクロスプラットフォームのマルチプロセス サポートを提供し、さまざまなオペレーティング システムで使用できます。

要約すると、concurrent.futures.ProcessPoolExecutorこれは基礎となるマルチプロセス関数をカプセル化する高レベルのインターフェイスであり、単純なマルチプロセス タスクの並列化に適しています。むしろ、multiprocessingこれはより多くの制御と柔軟性を提供する下位レベルのライブラリであり、プロセスのきめ細かい制御が必要なシナリオに適しています。

単純なタスクの並列化だけであればコードを簡素化するために使用でき、concurrent.futures.ProcessPoolExecutor下位レベルの制御や通信が必要な場合にはmultiprocessingライブラリを使用するなど、ニーズに応じて適切なライブラリを選択する必要があります。

やっと

マルチスレッドとは異なり、Asyncio はシングルスレッドですが、内部イベント ループ メカニズムにより、複数の異なるタスクを同時に実行でき、マルチスレッドよりも優れた自律性を享受できます。

Asyncio のタスクは実行プロセス中に中断されないため、競合状態は発生しません。

特に大量の I/O 操作のシナリオでは、Asyncio はマルチスレッドよりも効率的です。Asyncio の内部タスク切り替えによる損失は、スレッド切り替えによる損失よりもはるかに小さく、Asyncio がオープンできるタスクの数は、マルチスレッドのスレッドの数よりもはるかに多いためです。

ただし、多くの場合、Asyncio を使用するには、前の例の aiohttp など、特定のサードパーティ ライブラリのサポートが必要であることに注意してください。また、I/O 操作が高速で重くない場合は、マルチスレッドを使用することで問題を効果的に解決できます。

  • asyncio非同期プログラミングを実装するための Python ライブラリです。
  • コルーチンは、 、 、 およびキーワードをasyncio介して非同期操作を実現するための中心的な概念ですasyncawait
  • asyncioI/O 集中型のタスクを簡単に処理するための強力な非同期 I/O 操作 API を提供します。
  • asyncio.gather()etc. メカニズムを通じて、複数のコルーチン タスクを同時に実行できます。

最後に:以下の完全なソフトウェア テスト ビデオ チュートリアルが整理されてアップロードされており、必要な友人は自分で入手できます[100% 無料保証]

ソフトウェアテストの面接ドキュメント

私たちは高給の仕事を見つけるために勉強しなければなりません。次の面接の質問は、アリ、テンセント、バイトなどの一流インターネット企業からの最新の面接資料であり、一部のバイトの上司が権威ある回答をしています。このセットを完了してください。面接資料は次のとおりです。誰もが満足のいく仕事を見つけることができると信じています。

おすすめ

転載: blog.csdn.net/AI_Green/article/details/132564298