nikkie-ftnextの日記

イベントレポートや読書メモを発信

8/18(木)のみんなのPython勉強会 #stapy 非同期処理特集に向けて asyncio を予習するのです

はじめに

📣非同期処理に関するトークが大集合!nikkieです。

スタッフもしているみんなのPython勉強会、8月は非同期処理に関するトークが3本です!

非常に楽しみなのですが、「ぶっちゃけasync/awaitってあんまり使ったことないんだよね」という後ろめたさがあり、せめて少しでも話を合わせるために予習することにしました(←ぶっちゃけすぎ)。
asyncio、完全に理解した!(のですが、近いうちに何も分からなくなると思います。そういうものです1

目次

予習教材

8月の登壇者のうちお二人(陶山さんと福田さん)は非同期処理に関して執筆されているんです!
予習として、読んで手を動かすことにしました。

陶山さん『Python実践入門』

  • 第10章 並行処理
    • 10.1 並行処理と並列処理
      • 👉並行処理・並列処理・非同期処理といった用語が整理されました
      • 「非同期処理を利用しているときには、並行処理になっている」
    • 10.3 asyncioモジュール
      • イベントループを利用した並行処理
      • 後半のイベントループやタスクといった概念の説明部分は飛ばしました
      • 前半だけでも「async/awaitってなんなのか」完全に理解しました
      • aiohttpというライブラリがなんで存在するのかよくわかりました(恥ずかしながらasyncioと何が違うの?と思ってた)

福田さん『Python実践レシピ』

※実践レシピ自体は共著で、福田さんは並行処理、並列処理の章を書かれています。

  • Chapter19 並行処理,並列処理
    • 19.1 イベントループでの非同期処理―asyncio
      • 👉asyncioを使っても並行処理にならない例から、タスク・イベントループという概念も完全に理解しました

おことわり:asyncioに絞っています

Pythonでの並行処理にはasyncio(イベントループ)を使う以外に、マルチスレッドやマルチプロセスもあります。
これらも上記の2冊で扱われていますが、今回はasyncioと仲良くなりたかったので絞りました。

また、この記事は完全に理解するまでの素振りを綴ったアウトプットです。
asyncioを理解されたい場合は、この記事よりも紹介した書籍のどちらかを読むことを強くオススメします。

asyncioとasync/await

現時点での理解です

  • コルーチン:一連の処理の途中で中断・再開できる
    • ルーチン:一連の処理2
    • サブルーチン:呼び出したら最後まで実行される一連の処理。例:関数。中断・再開はできない
  • async/awaitはコルーチンの定義に関わる!
    • async defはコルーチンを定義している
    • awaitはコルーチンの内部に処理を中断できるポイントを作っている
    • 処理のまとまりのうち「こことここは中断できる」(コルーチン)として人が定義さえすれば、あとはasyncioがいい感じにやってくれるということか!
  • コルーチンの実行
    • asyncio.runでコルーチンを実行できる
    • asyncio.gatherで複数のコルーチンを並行実行できる
      • 返り値は渡したコルーチンの順
      • コルーチンをタスクにして呼び出すこともできる

過去に逐次処理で書いたコードを書き直してみた

過去に複数の画像をダウンロードしたいシーンがあり、逐次処理で実装していました。
これをasyncio.gatherを使って並行処理に書き直したところ、処理時間が劇的に短時間に!

async def download_images_async(tweets, output_root):
    async with httpx.AsyncClient() as client:
        download_coroutines = []
        for tweet in tweets:
            for attachment in tweet["attachments"]:
                src_url = attachment["url"]
                dest_path = output_root / build_image_path(src_url)
                # download_asyncは別のコルーチン
                download_coroutines.append(download_async(client, src_url, dest_path))
        _ = await asyncio.gather(*download_coroutines)

# 以下はエントリポイントにて
asyncio.run(
    download_images_async(tweet_iterator(args.tweets_jsonl), args.output_root)
)

詳しくは https://github.com/ftnext/anime-fan-python/blob/b669f68ad9ff02fd82e9508763a1e20a390ff20f/ainouta-pilgrim/download_tweet_images.py をどうぞ(L36-L60、L73-L77)。

動かして早速効果を体感しました!(上のツイート参照)
Web API呼び出しなど、使えるところには積極的に使って実践して練度を高めていきたいですね。

上のコードは全部一度にgatherするので、tweetsが千や万あるとすると、短時間に大量のリクエストが立て続けに送られるように思われます。
伸びしろとして、大量のtweetsでも負荷を与えすぎないよう、tweetsを区切って一度のアクセス数も絞る必要がありそうです(more-itertoolsのchunksなど)。

Webの通信は非同期にできた(HTTPXを利用)後で、「他のIOとしてファイルIOも非同期にできるのでは」と気づき、aiofilesも試そうと思っています。
Node.jsで書くコードと同様のものがPythonでも書けるってことなんですね!

終わりに

async/awaitでコルーチンを定義し、asyncioはイベントループでコルーチンを並行処理しているんですね!
手元のコードを書き換えて小さい成功体験も味わえました。

みんなのPython勉強会では著者に聞ける機会もあります(発表後の質疑や懇親会)。
仮にこの予習で不明点が解消しなかったら当日聞いてもよいわけです。
紹介した2冊を読んだが完全に理解までまだできていないかもという方、ぜひ当日質問しにお越しください!
私の場合は当日までに「何も分からない」状態になれたら、著者お二人に泣きついて、何も分からないから抜け出すヒントが得られそう!

追伸:「避けずに少し勉強する」の効果を知る

今回のasyncioの素振りをまとめていて思い出したのはこちら

「asyncioやasync/awaitってなんかいかついなあ」と避けてましたが、ちょっと時間をとって完全に理解までやったところ、Web API呼び出しの実装の武器が増えたと明確に実感しました🙌
やったね!(このあと何も分からなくなるとは思いますが、それも含めて楽しんでいきたい)



  1. ダニング・クルーガー効果と呼ばれるもののことです

  2. 「実装で知るasyncio」を聴いてコルーチンを全く分かっていないことに気づいた - mizzsugar’s blog 用語が整理されていて分かりやすかったです