Pythonで非同期を始めましょう

非同期プログラミング、または略して非同期は、プログラムが複数の操作を待機したり、それらのいずれかにハングアップしたりすることなくジャグリングできるようにする多くの現代言語の機能です。これは、ネットワークやファイルI / Oなどのタスクを効率的に処理するためのスマートな方法であり、プログラムの時間のほとんどはタスクの完了を待つために費やされます。

100個のネットワーク接続を開くWebスクレイピングアプリケーションについて考えてみます。1つの接続を開いて結果を待ち、次に次の接続を開いて結果を待つというようになります。プログラムの実行時間のほとんどは、実際の作業ではなく、ネットワーク応答の待機に費やされます。

非同期は、より効率的な方法を提供します。100の接続すべてを一度に開き、結果が返されるときにアクティブな各接続を切り替えます。1つの接続が結果を返さない場合は、すべての接続がデータを返すまで、次の接続に切り替えます。

非同期構文はPythonの標準機能になりましたが、一度に1つのことを行うことに慣れている長年のPythonistaは、頭を包み込むのに苦労するかもしれません。この記事では、Pythonで非同期プログラミングがどのように機能するか、およびそれを使用する方法について説明します。

Pythonで非同期を使用する場合は、Python3.7またはPython3.8(この記事の執筆時点での最新バージョン)を使用するのが最適であることに注意してください。これらのバージョンの言語で定義されているPythonの非同期構文とヘルパー関数を使用します。

非同期プログラミングを使用する場合

一般に、非同期を使用するのに最適なのは、次の特性を持つ作業を行おうとしているときです。

  • 作業が完了するまでに長い時間がかかります。
  • 遅延には、計算ではなく、I / O(ディスクまたはネットワーク)操作の待機が含まれます。
  • この作業には、一度に発生する多くのI / O操作、または他のタスクを実行しようとしているときに発生する1つ以上のI / O操作が含まれます。

Asyncを使用すると、アプリケーションの残りの部分をブロックすることなく、複数のタスクを並行してセットアップし、それらを効率的に反復できます。

asyncでうまく機能するタスクのいくつかの例:

  • 上記のように、Webスクレイピング。
  • ネットワークサービス(Webサーバーやフレームワークなど)。
  • 値を返すのに長い時間がかかる複数のソースからの結果を調整するプログラム(たとえば、同時データベースクエリ)。

非同期プログラミングは、マルチスレッドまたはマルチプロセッシングとは異なることに注意することが重要です。非同期操作はすべて同じスレッドで実行されますが、必要に応じて相互に譲り合うため、さまざまな種類のタスクでスレッド化やマルチプロセッシングよりも非同期が効率的になります。(これについては以下で詳しく説明します。)

Pythonasyncawaitasyncio

Pythonは最近、非同期操作を作成するために2つのキーワードとasyncを追加しましたawait。このスクリプトを検討してください。

def get_server_status(server_addr)#実行時間が長くなる可能性のある操作... return server_status def server_ops()results = [] results.append(get_server_status( 'addr1.server')results.append(get_server_status( 'addr2.server')return結果 

同じスクリプトの非同期バージョン(機能的ではなく、構文がどのように機能するかを理解するのに十分)は、次のようになります。

async def get_server_status(server_addr)#実行時間が長くなる可能性のある操作... return server_status async def server_ops()results = [] results.append(await get_server_status( 'addr1.server')results.append(await get_server_status( 'addr2。サーバー ')結果を返す 

asyncキーワードのプレフィックスが付いた関数は、コルーチンとも呼ばれる非同期関数になります。コルーチンは通常の関数とは異なる動作をします。

  • コルーチンは別のキーワードを使用できますawait。これにより、コルーチンはブロックせずに別のコルーチンからの結果を待つことができます。awaitedコルーチンから結果が返されるまで、Pythonは実行中の他のコルーチン間で自由に切り替わります。
  • コルーチンは、他の関数からのみ呼び出すことができasyncます。スクリプトの本文から実行するserver_ops()get_server_status()、そのまま実行すると、結果は得られません。直接使用できないPythonコルーチンオブジェクトを取得します。

では、async非同期関数から関数を呼び出すことができず、async関数を直接実行できない場合、それらをどのように使用するのでしょうか。回答:Pythonの残りの部分asyncioをブリッジするライブラリを使用するasync

Pythonasyncawaitasyncio

これは、asyncとを使用してWebスクレイピングアプリケーションを作成する方法の例です(これも機能的ではありませんが、説明になります)asyncio。このスクリプトは、URLのリストを取得asyncし、外部ライブラリ(read_from_site_async())から関数の複数のインスタンスを使用してそれらをダウンロードし、結果を集約します。

import asyncio from web_scraping_library import read_from_site_async async def main(url_list):return await asyncio.gather(* [read_from_site_async(_)for _ in url_list])urls = ['// site1.com'、 '// othersite.com'、 '//newsite.com'] results = asyncio.run(main(urls))print(results) 

上記の例では、2つの一般的なasyncio関数を使用しています。

  • asyncio.run()async、コードの非同期部分から関数を起動するために使用されます。これにより、プログラムのすべての非同期アクティビティが開始されます。(これが私たちの実行方法main()です。)
  • asyncio.gather()1つ以上の非同期で装飾された関数(この場合、read_from_site_async()架空のWebスクレイピングライブラリからのいくつかのインスタンス)を取得し、それらすべてを実行して、すべての結果が届くのを待ちます。

ここでの考え方は、すべてのサイトの読み取り操作を一度に開始し、到着時に結果を収集することです(したがってasyncio.gather())。1つの操作が完了するのを待たずに、次の操作に進みます。

Python非同期アプリのコンポーネント

Python非同期アプリがコルーチンを主要な要素として使用し、asyncioライブラリを利用してコルーチンを実行する方法については、すでに説明しました。他のいくつかの要素もPythonの非同期アプリケーションの鍵です。

イベントループ

asyncioライブラリが作成および管理イベントループ、彼らが完了するまで、コルーチンを実行するメカニズムを。プログラマーが何が入っているかを追跡しやすくするためだけに、Pythonプロセスで一度に1つのイベントループのみを実行する必要があります。

タスク

処理のためにコルーチンをイベントループに送信すると、Taskオブジェクトを取り戻すことができます。これにより、イベントループの外部からコルーチンの動作を制御できます。たとえば、実行中のタスクをキャンセルする必要がある場合は、タスクの.cancel()メソッドを呼び出すことでキャンセルできます。

これは、イベントループと動作中のタスクを示すサイトスクレイパースクリプトのわずかに異なるバージョンです。

import asyncio from web_scraping_library import read_from_site_async tasks = [] async def main(url_list):for n in url_list:tasks.append(asyncio.create_task(read_from_site_async(n)))print(tasks)return await asyncio.gather(* tasks)urls = ['//site1.com'、'// othersite.com'、 '// newsite.com'] loop = asyncio.get_event_loop()results = loop.run_until_complete(main(urls))print(results) 

このスクリプトは、イベントループとタスクオブジェクトをより明示的に使用します。

  • この.get_event_loop()メソッドは、を介してプログラムで非同期関数を送信することにより、イベントループを直接制御できるオブジェクトを提供します.run_until_complete()。前のスクリプトでは、を使用して1つのトップレベルの非同期関数しか実行できませんasyncio.run()でした。ちなみに、.run_until_complete() それはまさにそれが言うことをします:それはそれらが完了するまで提供されたすべてのタスクを実行し、そしてそれらの結果を単一のバッチで返します。
  • この.create_task()メソッドは、パラメーターを含めて実行する関数を受け取り、Taskそれを実行するためのオブジェクトを返します。ここでは、各URLを個別Taskにイベントループに送信し、Taskオブジェクトをリストに格納します。これは、イベントループ内、つまりasync関数内でのみ実行できることに注意してください。

イベントループとそのタスクをどの程度制御する必要があるかは、構築しているアプリケーションの複雑さによって異なります。Webスクレイパーのように、一連の固定ジョブを送信して同時に実行する場合は、ジョブを起動して結果を収集するのに十分なだけの、多くの制御は必要ありません。 

対照的に、本格的なWebフレームワークを作成している場合は、コルーチンとイベントループの動作をはるかに細かく制御する必要があります。たとえば、アプリケーションがクラッシュした場合にイベントループを正常にシャットダウンする必要がある場合や、別のスレッドからイベントループを呼び出す場合は、スレッドセーフな方法でタスクを実行する必要がある場合があります。

非同期vs.スレッドvs.マルチプロセッシング

この時点で、なぜPythonで長い間利用されてきたスレッドやマルチプロセッシングの代わりに非同期を使用するのか疑問に思われるかもしれません。

First, there is a key difference between async and threads or multiprocessing, even apart from how those things are implemented in Python. Async is about concurrency, while threads and multiprocessing are about parallelism. Concurrency involves dividing time efficiently among multiple tasks at once—e.g., checking your email while waiting for a register at the grocery store. Parallelism involves multiple agents processing multiple tasks side by side—e.g., having five separate registers open at the grocery store.

Most of the time, async is a good substitute for threading as threading is implemented in Python. This is because Python doesn’t use OS threads but its own cooperative threads, where only one thread is ever running at a time in the interpreter. In comparison to cooperative threads, async provides some key advantages:

  • Async functions are far more lightweight than threads. Tens of thousands of asynchronous operations running at once will have far less overhead than tens of thousands of threads.
  • The structure of async code makes it easier to reason about where tasks pick up and leave off. This means data races and thread safety are less of an issue. Because all tasks in the async event loop run in a single thread, it’s easier for Python (and the developer) to serialize how they access objects in memory.
  • Async operations can be cancelled and manipulated more readily than threads. The Task object we get back from asyncio.create_task() provides us with a handy way to do this.

Multiprocessing in Python, on the other hand, is best for jobs that are heavily CPU-bound rather than I/O-bound. Async actually works hand-in-hand with multiprocessing, as you can use asyncio.run_in_executor() to delegate CPU-intensive jobs to a process pool from a central process, without blocking that central process.

Next steps with Python async

The best first thing to do is build a few, simple async apps of your own. Good examples abound now that asynchronous programming in Python has undergone a few versions and had a couple of years to settle down and become more widely used. The official documentation for asyncio is worth reading over to see what it offers, even if you don’t plan to make use of all of its functions.

また、非同期を利用したライブラリやミドルウェアの数が増えていることを調べることもできます。これらの多くは、データベースコネクタ、ネットワークプロトコルなどの非同期の非ブロッキングバージョンを提供します。aio-libsリポジトリは、以下のようないくつかの重要なもの、持っているaiohittpウェブアクセスするためのライブラリを。asyncキーワードを使用してライブラリをPythonPackageIndexで検索することも価値があります。非同期プログラミングのようなものでは、学ぶための最良の方法は、他の人がそれをどのように使用しているかを確認することです。