はじめに
この記事でわかること
- 非同期プログラミングとは何か、そのメリットと用途
 - Pythonの
asyncioの内部構造とイベントループの仕組み async/awaitの基本的な使い方と注意点asyncioを使った並行処理の実践例- 同期処理とのパフォーマンス比較
 - 実際のアプリケーションでの応用例(Webスクレイピングやネットワーク通信)
 
1. 非同期プログラミングとは?
非同期プログラミングは、複数の処理を並行して実行できる手法です。通常の(同期的な)プログラムは、一つの処理が終わるまで次の処理を開始できません。一方、非同期プログラムでは、時間のかかる処理(例えばネットワーク通信やファイル操作)を待つ間に、他の処理を進めることができます。
非同期のメリット
- 効率的なCPU利用: I/O待ちの時間を他の処理に活用
 - レスポンスの向上: サーバーアプリケーションなどで、リクエストを効率的に処理
 - スケーラビリティ: 多数の接続をさばく必要があるアプリケーションに適用可能
 
次に、Pythonのasyncioを用いた非同期処理の基本を解説します。
2. asyncioの基本 – イベントループとタスク
Pythonのasyncioは、非同期プログラミングをサポートする標準ライブラリです。asyncioの中心的な概念は以下の3つです。
- イベントループ: 非同期タスクを管理し、スケジューリングする役割
 - コルーチン(coroutine): 
async/awaitを使用する非同期関数 - タスク(Task): イベントループ上で実行されるコルーチン
 
基本的なasync / await の使い方
import asyncio
async def hello():
    print("Hello")
    await asyncio.sleep(1)  # 1秒待機(非同期)
    print("World")
asyncio.run(hello())
このコードでは、hello関数が1秒間待機する間に他の処理が可能になります。
実行すると、”Hello”を出力したのちに”World”が出力されます。

次に、asyncioの内部構造を詳しく見ていきましょう。
3. asyncioの内部構造
Pythonのasyncioの中核となるのが「イベントループ」です。イベントループは、非同期タスクをスケジュールし、適切なタイミングで処理を実行する仕組みです。
イベントループの仕組み
- タスクの登録: 
async関数が呼ばれると、コルーチンオブジェクトが生成される。 - タスクのスケジュール: 
asyncio.create_task()でタスクがスケジュールされる。 - イベントループの実行: 
asyncio.run()により、イベントループが開始され、登録されたタスクが順番に実行される。 
タスクの実行例
import asyncio
async def task1():
    print("Task 1 start")
    await asyncio.sleep(2)  # 2秒待機(非同期)
    print("Task 1 end")
async def task2():
    print("Task 2 start")
    await asyncio.sleep(1)  # 1秒待機(非同期)
    print("Task 2 end")
async def main():
    t1 = asyncio.create_task(task1())  # タスク1をスケジュール
    t2 = asyncio.create_task(task2())  # タスク2をスケジュール
    await t1  # タスク1の完了を待つ
    await t2  # タスク2の完了を待つ
asyncio.run(main())
このコードでは、task1とtask2が並行して実行されます。
4. 実践: asyncio を活用した並行処理
複数の非同期タスクを並行実行することで、効率的な処理を実現できます。
import asyncio
async def fetch_data(id):
    print(f"Fetching data {id}...")
    await asyncio.sleep(2)  # 2秒待機(非同期)
    print(f"Data {id} received")
async def main():
    tasks = [fetch_data(i) for i in range(5)]  # 5つの非同期タスクを作成
    await asyncio.gather(*tasks)  # すべてのタスクを並行実行
asyncio.run(main())
5. パフォーマンス比較 – 同期 vs 非同期
同期処理と非同期処理の違いを比較します。
同期処理の例
最初に同期処理の例を見てみます。
今回実行するコードでは、2つのタスクを実行します。
各タスクは2秒を想定します。
それでは見てみましょう。
import time
def sync_task():
    print("Starting task...")
    time.sleep(2)  # 2秒待機(同期処理)
    print("Task completed")
start = time.time()
sync_task()
sync_task()
print(f"Sync execution time: {time.time() - start:.2f} seconds")
このコードを実行してみます。
2つのタスクを同期(1つずつ順番)処理しているため、4秒(2秒のタスク×2回)かかっています。

非同期処理の例
次に、非同期処理で試してみます。
import asyncio
import time
async def async_task():
    print("Starting async task...")
    await asyncio.sleep(2)  # 2秒待機(非同期処理)
    print("Async task completed")
async def main():
    task1 = asyncio.create_task(async_task())
    task2 = asyncio.create_task(async_task())
    await task1
    await task2
start = time.time()
asyncio.run(main())
print(f"Async execution time: {time.time() - start:.2f} seconds")
実行結果としては、2.02秒でした。
若干の誤差はありますが、この非同期処理では、2つのタスクが並行して実行されるため、合計の処理時間が短縮されます。

6. ケーススタディ: asyncio を使ったアプリケーション
今回紹介した、asyncioモジュールを使用した非同期プログラミングの基本的な概念
1. Webスクレイピング(aiohttpとBeautifulSoupを使用)
aiohttpを使用して非同期にWebページをダウンロードし、BeautifulSoupを使用してHTMLを解析します。 複数のURLを同時にスクレイピングすることで、処理時間を短縮します。
import asyncio
import aiohttp
from bs4 import BeautifulSoup
async def fetch_url(session, url):
    async with session.get(url) as response:
        return await response.text()
async def scrape_data(url):
    async with aiohttp.ClientSession() as session:
        html = await fetch_url(session, url)
        soup = BeautifulSoup(html, 'html.parser')
        # ここでBeautifulSoupを使用してデータを抽出
        title = soup.title.string
        return title
async def main():
    urls = ['https://www.example.com', 'https://www.example.org', 'https://www.example.net']
    tasks = [asyncio.create_task(scrape_data(url)) for url in urls]
    results = await asyncio.gather(*tasks)
    for result in results:
        print(result)
if __name__ == '__main__':
    asyncio.run(main())
2. API通信(aiohttpを使用)
aiohttpを使用して非同期にAPIリクエストを送信し、JSON形式のレスポンスを処理します。 複数のAPIリクエストを同時に送信することで、効率的なデータ取得を実現します。
import asyncio
import aiohttp
import json
async def fetch_api(session, url):
    async with session.get(url) as response:
        return await response.json()
async def main():
    api_url = 'https://jsonplaceholder.typicode.com/posts/1'
    async with aiohttp.ClientSession() as session:
        data = await fetch_api(session, api_url)
        print(json.dumps(data, indent=2))
if __name__ == '__main__':
    asyncio.run(main())
3. 非同期Webサーバー(aiohttpを使用)
aiohttp.webを使用して非同期Webサーバーを構築します。 複数のクライアントからのリクエストを同時に処理し、高いパフォーマンスを実現します。
import asyncio
from aiohttp import web
async def handle(request):
    name = request.match_info.get('name', "Anonymous")
    text = "Hello, " + name
    return web.Response(text=text)
async def main():
    app = web.Application()
    app.add_routes([web.get('/', handle),
                    web.get('/{name}', handle)])
    runner = web.AppRunner(app)
    await runner.setup()
    site = web.TCPSite(runner, 'localhost', 8080)
    await site.start()
    print("Server started at http://localhost:8080")
    # サーバーを永続的に実行
    await asyncio.Event().wait()
if __name__ == '__main__':
    asyncio.run(main())
7. まとめ
非同期プログラミングは、I/O待ち時間を効率的に活用し、アプリケーションのパフォーマンスを向上させます。asyncioの内部構造を理解し、適切に活用することで、より高速でスケーラブルなアプリケーションを開発できます。
  
  
  
  

コメント