並列処理プログラミングのメモ#6 (イベント)
| 登録日 | :2025/05/10 10:41 |
|---|---|
| カテゴリ | :Python基礎 |
busy-waitingアプローチの欠点に対応する方法の一つとして「イベント」がある。
イベントベースの処理は、スレッドやプロセスではなく、イベント(メッセージ:例えばこの処理が終わったからリソース空いたよとメッセージを出すなど)を中心としてアプリケーションを構造化する。
イベントベースの並行処理や、Webサーバや、メッセージングシステム、ゲームプラットフォームなど多くのハイパフォーマンスアプリケーションで見つかる。
コールバック
コールバック:イベント駆動形プログラムにおいて、各イベントが発生した時に実行されるコード
コールバックベースのコードでは、制御フローがわかりにくくなったり、デバックが難しくなったりしがちである。(コードがもやは逐次処理ではないため。ロジックを複数のコールバックに分散すると、コードでの処理の連鎖により、コールバックが何重にも入れ子になることがあり、これをコールバック地獄という)
イベントループ
イベントループ:さまざまなイベントとそれに対応するコールバックを組み合わせて制御するエンティティ
イベントループでは、イベントは「イベントキュー」に追加される。
(busy-waitingの場合は、イベントポーリングを使っていた)
イベントループでは、イベントキューからイベントを取り出して適切なコールバックを呼び出すという処理を繰り返す。
以下のサンプルコードでは、イベントループを作成し、knockとwhoの2つのイベントを登録している(knockイベントがwhoイベントを生成できることに注意)。
続いて、knockイベントを二つ生成して、それがちょうど発生したかのような状況を再現し、イベントループを無限ループとして開始する。
"""
Chapter 11 / event_loop.py
"""
from __future__ import annotations
from collections import deque
from time import sleep
import typing as T
class Event:
"""
Eventクラスはイベントループによって実行されるアクション
"""
def __init__(self, name: str, action: T.Callable[..., None],
next_event: T.Optional[Event] = None) -> None:
self.name = name
self._action = action
self._next_event = next_event
def execute_action(self) -> None:
self._action(self)
if self._next_event:
event_loop.register_event(self._next_event)
class EventLoop:
def __init__(self) -> None:
"""
イベントループによって実行されるイベントの格納先となるキューを作成
"""
self._events: deque[Event] = deque()
def register_event(self, event: Event) -> None:
# イベントキューにイベントを追加
self._events.append(event)
def run_forever(self) -> None:
"""
イベントループを無限ループとして開始
キューに追加されたイベントをそれぞれ実行
"""
print(f"Queue running with {len(self._events)} events")
while True:
try:
event = self._events.popleft()
except IndexError:
continue
event.execute_action()
def knock(event: Event) -> None:
print(event.name)
sleep(1)
def who(event: Event) -> None:
print(event.name)
sleep(1)
if __name__ == "__main__":
event_loop = EventLoop()
replying = Event("Who's there", who)
knocking = Event("Knock-knock", knock, replying)
for _ in range(2):
event_loop.register_event(knocking)
event_loop.run_forever()