はじめに
この記事でわかること
- 非同期プログラミングとは何か、そのメリットと用途
- 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
の内部構造を理解し、適切に活用することで、より高速でスケーラブルなアプリケーションを開発できます。
コメント