Python非同期処理入門 第1部:なぜasyncioが必要なのか?基礎から理解する

プログラミングを学習していると「非同期処理」という言葉を聞くことがあります。「難しそう…」と感じるかもしれませんが、実は日常生活の中にも非同期処理の考え方があります。この記事では、Pythonの非同期処理(asyncio)について、初心者でも理解できるように基礎から解説します。

同期処理と非同期処理:レストランの例で理解する

同期処理=一人のウェイターが全部やる

想像してください。レストランに一人のウェイターしかいません。このウェイターは:

  1. お客様Aの注文を聞く(30秒)
  2. 厨房に注文を伝える(10秒)
  3. 料理ができるまで待つ(5分)
  4. 料理を運ぶ(30秒)
  5. お客様Bの対応を始める

この方法だと、お客様Bは約6分も待たされてしまいます。これが同期処理です。

非同期処理=効率的に動くウェイター

賢いウェイターなら:

  1. お客様Aの注文を聞く(30秒)
  2. 厨房に注文を伝える(10秒)
  3. 料理を待つ間にお客様Bの注文を聞く(30秒)
  4. お客様Bの注文も厨房に伝える(10秒)
  5. 料理ができたものから順に運ぶ

これが非同期処理の考え方です。待ち時間を有効活用しています。

Pythonコードで見る違い

同期処理の例(遅い)

import time

def cook_pasta():
    """パスタを作る(3秒かかる)"""
    print("パスタを作り始めました...")
    time.sleep(3)  # 3秒待つ
    print("パスタが完成しました!")
    return "パスタ"

def cook_salad():
    """サラダを作る(2秒かかる)"""
    print("サラダを作り始めました...")
    time.sleep(2)  # 2秒待つ
    print("サラダが完成しました!")
    return "サラダ"

# 順番に料理を作る
start_time = time.time()

pasta = cook_pasta()  # 3秒待つ
salad = cook_salad()  # さらに2秒待つ

end_time = time.time()
print(f"n完成した料理: {pasta}と{salad}")
print(f"かかった時間: {end_time - start_time:.1f}秒")

# 結果: 約5秒かかる

このコードを実行すると、パスタが完成してからサラダを作り始めるので、合計5秒かかります。

非同期処理の例(速い)

import asyncio

async def cook_pasta_async():
    """パスタを非同期で作る"""
    print("パスタを作り始めました...")
    await asyncio.sleep(3)  # 3秒待つ(非同期)
    print("パスタが完成しました!")
    return "パスタ"

async def cook_salad_async():
    """サラダを非同期で作る"""
    print("サラダを作り始めました...")
    await asyncio.sleep(2)  # 2秒待つ(非同期)
    print("サラダが完成しました!")
    return "サラダ"

async def main():
    """メイン処理"""
    # 両方の料理を同時に開始
    results = await asyncio.gather(
        cook_pasta_async(),
        cook_salad_async()
    )
    return results

# 実行
start_time = time.time()
pasta, salad = asyncio.run(main())
end_time = time.time()

print(f"n完成した料理: {pasta}と{salad}")
print(f"かかった時間: {end_time - start_time:.1f}秒")

# 結果: 約3秒で完成(速い方の時間)

同じ料理を作るのに、非同期処理では3秒で済みます!

asyncioの重要な3つのキーワード

1. async def(非同期関数の定義)

async def my_async_function():
    """これは非同期関数です"""
    return "Hello"

async defで始まる関数は「非同期関数」になります。普通の関数との違いは、内部でawaitを使えることです。

2. await(非同期処理を待つ)

async def example():
    result = await some_async_operation()
    print(result)

awaitは「この処理が終わるまで待って」という意味です。ただし、待っている間に他の処理を実行できます。

3. asyncio.run(非同期処理を実行)

asyncio.run(main())  # main()は非同期関数

非同期関数を実行するための特別な方法です。

よくある間違いと解決法

間違い1:awaitを忘れる

# ❌ 間違い
async def bad_example():
    result = cook_pasta_async()  # awaitを忘れている!
    print(result)  # <coroutine object...> と表示される

# ✅ 正解
async def good_example():
    result = await cook_pasta_async()
    print(result)  # "パスタ" と表示される

間違い2:普通の関数でawaitを使う

# ❌ 間違い
def normal_function():
    await asyncio.sleep(1)  # エラーになる!

# ✅ 正解
async def async_function():
    await asyncio.sleep(1)  # OK

実用例:複数のWebサイトをチェック

実際に役立つ例を見てみましょう。複数のWebサイトの応答時間を調べるプログラムです:

import asyncio
import aiohttp
import time

async def check_website(session, url):
    """Webサイトの応答時間を測定"""
    try:
        start = time.time()
        async with session.get(url) as response:
            await response.text()
            elapsed = time.time() - start
            return f"{url}: {elapsed:.2f}秒"
    except Exception as e:
        return f"{url}: エラー ({str(e)})"

async def check_all_websites():
    """複数のサイトを同時にチェック"""
    urls = [
        'https://www.google.com',
        'https://www.yahoo.co.jp',
        'https://www.amazon.co.jp'
    ]

    async with aiohttp.ClientSession() as session:
        # 全てのサイトを同時にチェック
        results = await asyncio.gather(
            *[check_website(session, url) for url in urls]
        )

        return results

# 実行
print("Webサイトの応答時間を測定中...")
results = asyncio.run(check_all_websites())

print("n結果:")
for result in results:
    print(result)

このプログラムは3つのサイトを同時にチェックするので、最も遅いサイトの時間で全体が完了します。

まとめ:いつ非同期処理を使うべき?

非同期処理が効果的な場面:

  1. ネットワーク通信(API呼び出し、Webスクレイピング)
  2. ファイルの読み書き(大量のファイル処理)
  3. データベース操作(複数のクエリ実行)

逆に、CPU を使う計算処理(数値計算など)では効果がありません。

次回の記事では、実際に仮想通貨の価格データを非同期で取得する方法を解説します。

練習問題

以下のコードを非同期処理に書き換えてみましょう:

# 同期処理版
def download_file(filename):
    print(f"{filename}のダウンロード開始")
    time.sleep(2)
    print(f"{filename}のダウンロード完了")
    return f"{filename}の内容"

# 3つのファイルを順番にダウンロード
file1 = download_file("data1.txt")
file2 = download_file("data2.txt")
file3 = download_file("data3.txt")

次回予告

第2部:CCXTで実践する非同期処理 – 複数取引所から価格を高速取得では、実際に仮想通貨の価格データを複数の取引所から同時に取得する方法を学びます。

関連記事

コメントする