Python asyncioでのシグナルハンドリングの基本と応用

この記事では、Pythonの`asyncio`を使用したシグナルハンドリングについて解説します。具体的なコード例とその詳細な解説、そして応用例を含めています。

目次

はじめに:シグナルハンドリングとは

シグナルハンドリングは、OSからプログラムに送られるイベント(シグナル)に対する処理を設定する手法です。非同期プログラミングにおいても、シグナルハンドリングは重要な役割を果たします。特に`asyncio`を使用した場合、非同期処理を効率よく管理できるため、注意が必要です。

基本的な使い方

まずは、基本的な`asyncio`でのシグナルハンドリングのコード例を見てみましょう。

import asyncio
import signal

async def main():
    # 長い処理を模倣
    print("処理を開始します")
    await asyncio.sleep(10)
    print("処理が完了しました")

def handler(signum):
    print(f"シグナル{signum}を受け取りました。処理を中止します。")
    loop.stop()

if __name__ == "__main__":
    loop = asyncio.get_event_loop()
    loop.add_signal_handler(signal.SIGINT, handler, signal.SIGINT)
    loop.run_until_complete(main())

コード解説

1. `import asyncio`と`import signal`で必要なモジュールをインポートしています。
2. `async def main():`で非同期のメイン関数を定義しています。
3. `def handler(signum):`でシグナルハンドラ(シグナルを受けた時の処理)を定義しています。
4. `loop.add_signal_handler(signal.SIGINT, handler, signal.SIGINT)`で、CTRL+C(SIGINT)が押された際に`handler`関数を呼び出すようにしています。

応用例1:複数のシグナルハンドリング

一つのプログラムで複数のシグナルをハンドリングする場合の例です。

import asyncio
import signal

async def main():
    print("処理を開始します")
    await asyncio.sleep(10)
    print("処理が完了しました")

def handler(signum):
    print(f"シグナル{signum}を受け取りました。処理を中止します。")
    loop.stop()

if __name__ == "__main__":
    loop = asyncio.get_event_loop()
    loop.add_signal_handler(signal.SIGINT, handler, signal.SIGINT)
    loop.add_signal_handler(signal.SIGTERM, handler, signal.SIGTERM)
    loop.run_until_complete(main())

コード解説

この例では、`loop.add_signal_handler`を二回呼び出して、SIGINTとSIGTERMの両方のシグナルに対応しています。

応用例2:タスクの安全なキャンセル

シグナルを受け取った際に、実行中の非同期タスクを安全にキャンセルする方法です。

import asyncio
import signal

async def main():
    print("処理を開始します")
    await asyncio.sleep(10)
    print("処理が完了しました")

async def shutdown(signal, loop):
    print(f"シグナル{signal}を受け取りました。処理を中止します。")
    tasks = [t for t in asyncio.all_tasks() if t is not asyncio.current_task()]
    [task.cancel() for task in tasks]
    await asyncio.gather(*tasks, return_exceptions=True)
    loop.stop()

if __name__ == "__main__":
    loop = asyncio.get_event_loop()
    for sig in [signal.SIGINT, signal.SIGTERM]:
        loop.add_signal_handler(sig, lambda sig=sig: asyncio.create_task(shutdown(sig, loop)))
    loop.run_until_complete(main())

コード解説

こちらの例では、`shutdown`関数内で`asyncio.all_tasks()`を使用して現在実行中の全てのタスクを取得し、それらを安全にキャンセルしています。

まとめ

`asyncio`を使用したシグナルハンドリングは、非同期処理が多用される現代のアプリケーション開発において、非常に有用です。基本的な使い方から応用例まで、様々なケースでのシグナルハンドリングの方法を理解することで、より堅牢なアプリケーションを作成することができるでしょう。

コメント

コメントする

目次