Instant Engineering

エンジニアの仕事効率を上げる知識をシェアするWeb記事/機械設計/TPS/QC品質管理

イベント駆動とは|周期処理とアーキテクチャ違い

工場の制御盤に並ぶPLCは、決まった周期でプログラムを繰り返し実行しています。

センサーが反応しようがしまいが、一定のスキャンタイムで入力を読み取り、出力を書き換える。
この「周期処理」は産業界の標準であり、多くの技術者にとって馴染み深い動作モデルです。

ところが近年、IoTやクラウド連携が進む製造現場では「イベントが起きたときだけ処理する」という異なる考え方が存在感を増しています。

それがイベント駆動(Event-Driven)と呼ばれるアーキテクチャです。

本記事では、イベント駆動の基本原理から周期処理との比較、組込みシステムやPLCとの関係、そして設計上の注意点までを体系的に解説します。

 

1. イベント駆動とは

イベント駆動とは、システムの外部または内部で発生する「イベント」をきっかけ(トリガー)として処理を実行するソフトウェアの動作モデルです。
英語では Event-Driven と表記され、日本語では「イベント駆動型」「イベントドリブン」とも呼ばれます。

従来のプログラムが「コードの先頭から末尾まで順番に実行する」という逐次処理を基本としていたのに対して、イベント駆動では「何かが起きたら、それに対応する処理を走らせる」という受動的な動作モデルを採用しています。
この違いは単なるプログラミングスタイルの差ではなく、システムの応答性、リソース効率、拡張性に根本的な影響を与えます。

イベントの定義と分類

イベントとは、システムにとって意味のある「状態変化」を指します。
変化の内容は多岐にわたりますが、大きく3つのカテゴリに分類できます。

第一にハードウェアイベントです。
温度センサーが閾値を超えた、ボタンが押された、モーターが定格回転数に到達した、といった物理的な状態変化がこれに該当します。
組込みシステムでは、GPIOピンの立ち上がりエッジやA/Dコンバータの変換完了割り込みとして検知されます。

第二にソフトウェアイベントです。
タイマーの満了、ファイルの書き込み完了、データベースへのレコード挿入、ネットワークパケットの到着などが代表例です。
OSのシステムコールやミドルウェアのAPIを通じてアプリケーションに通知されます。

第三にユーザーイベントです。
マウスのクリック、キーボード入力、タッチスクリーンの操作など、人間の操作に起因するイベントです。
GUIプログラミングでは、この種のイベントがアプリケーション動作の主要なトリガーとなります。

これらの変化がシステムに伝達されると、あらかじめ登録されていた処理(ハンドラやコールバック)が呼び出されます。
イベントの種類に応じて異なるハンドラを割り当てることで、システムは多様な状況に柔軟に対応できるようになります。

対比としての逐次処理・周期処理

イベント駆動の特徴をより鮮明にするために、他の実行モデルと比較しましょう。

逐次処理は、コードの先頭から末尾へ一直線に命令を実行するモデルです。
バッチ処理や単純な計算プログラムに適していますが、外部からの入力を待つ場面では「ビジーウェイト」(何もせず待ち続ける)に陥りやすい欠点があります。

周期処理は、一定の時間間隔で全処理を繰り返すモデルです。
PLCのスキャンサイクルや組込みシステムのメインループが代表例で、タイミングの予測が容易なため制御システムで広く採用されています。
しかし、何も変化がなくてもCPUを消費し続けるという非効率さがあります。

イベント駆動は、これらとは根本的に異なり、「処理の実行タイミングをイベントの発生に委ねる」という点が最大の特徴です。
何も起きていないときはCPUリソースを消費せず、変化があったときだけ素早く応答できます。

3つのモデルの実行効率を比較すると、イベント発生頻度が低い場合にイベント駆動の優位性が際立ちます。
ただし、決定論的な動作が保証されにくいというトレードオフがあり、安全性が最優先される制御システムでは慎重な検討が必要です。

適用分野の広がり

イベント駆動は、もともとGUI(グラフィカルユーザーインターフェース)プログラミングの文脈で普及しました。
1985年に登場した Windows 1.0 のメッセージループがその原点であり、以降すべてのGUIフレームワークがイベント駆動モデルを採用しています。

現在では、その適用範囲は飛躍的に広がっています。
Webサーバー(Node.js)は単一スレッドのイベントループで数万の同時接続を処理し、クラウドサービス(AWS Lambda)はイベントの到着に応じてコンテナを自動起動するサーバーレスアーキテクチャを実現しています。

マイクロサービス間の非同期通信では、Apache KafkaやRabbitMQといったメッセージブローカを介したイベント駆動型アーキテクチャ(EDA)が標準的な設計パターンです。
さらに、IoTゲートウェイや産業用エッジコンピューティングにまで適用範囲が広がっており、製造業の技術者にとっても無視できない技術になっています。

特に「Industry 4.0」や「スマートファクトリー」の文脈では、工場内の機器がイベント(異常検知、品質逸脱、稼働状態変化)をリアルタイムに発信し、上位システムが即座に対応するというイベント駆動型のアーキテクチャが注目されています。

 

2. 周期処理(ポーリング)との違い

イベント駆動を正しく理解するためには、対比として「周期処理」の仕組みを知ることが不可欠です。
ここでは両者の動作原理を比較し、数式を使って定量的にその違いを明らかにします。

周期処理(ポーリング)の動作原理

周期処理とは、あらかじめ決められた一定の時間間隔で、入力の読み取り→処理の実行→出力の書き換えを繰り返す方式です。
制御工学の分野では、この繰り返し周期をサンプリング周期  T_s と呼びます。

 

 T_s = \dfrac{1}{f_s}

 

ここで  f_s はサンプリング周波数です。
たとえばfs=1000Hzのとき Ts=1msとなり、1ミリ秒ごとに全入力をスキャンすることを意味します。

ポーリング(Polling)はこの周期処理の一形態であり、CPUがソフトウェア的にデバイスの状態レジスタを定期的に確認する方式を指します。
仕組みが単純なため実装が容易で、タイミングの予測がしやすいという利点があります。

ポーリングの典型的なコードは次のような構造です。

while True:
    for device in all_devices:
        status = read_register(device)
        if status_changed(status):
            process(device, status)
    sleep(T_s)

この構造では、すべてのデバイスを毎回スキャンするため、監視対象が増えると1回のスキャンに要する時間が増大します。

イベント駆動の動作原理

一方、イベント駆動では「変化が起きた瞬間」にのみ処理が走ります。

入力の変化がイベントとしてキューに投入され、イベントループがキューから取り出して対応するハンドラを実行するという流れです。
変化がなければ、ループは待機状態のままCPUを解放します。

応答時間はイベント検知から処理完了までの遅延  t_{\text{resp}} で評価されます。

 

 t_{\text{resp}} = t_{\text{detect}} + t_{\text{queue}} + t_{\text{exec}}

 

ここで tdetectはイベント検知遅延、tqueueはキュー待ち時間、texecはハンドラの実行時間です。
キューが空の状態でイベントが到着した場合、tqueue≈0となり、ほぼ即座にハンドラが起動されます。

比較表で整理する

周期処理とイベント駆動の主要な違いを表にまとめます。

比較項目 周期処理(ポーリング) イベント駆動
処理の起動条件 時間経過(タイマー) イベントの発生
CPU負荷 常に一定(変化がなくてもスキャン) イベント発生時のみ増加
応答の最悪遅延 最大Ts(1周期分) キュー深さとハンドラ実行時間に依存
実装の容易さ 単純で見通しが良い 非同期設計が必要でやや複雑
スケーラビリティ 入力数増加で周期が延びる 入力数増加に強い
決定論的動作 高い(周期が固定) 低い(イベント発生順に依存)
消費電力 高い(常時スキャン) 低い(待機時はスリープ可)

 

応答遅延の定量的な違い

ポーリング方式では、最悪の場合、イベント発生直後にスキャンが完了したタイミングだと、次のスキャンまで丸1周期待つことになります。
平均応答遅延は次のように見積もれます。

 

 \bar{t}_{\text{poll}} = \dfrac{T_s}{2} + t_{\text{exec}}

 

たとえば Ts=10ms で texec=0.5ms の場合、平均応答遅延は5.5msです。
最悪の場合は Ts+texec=10.5ms まで遅延する可能性があります。

一方、イベント駆動では検知遅延がハードウェア割り込みに近いレベルまで短縮できるため、理想的には次の関係が成り立ちます。

 

 t_{\text{resp}} \ll \bar{t}_{\text{poll}} \quad (\text{イベント頻度が低い場合})

 

ただし、大量のイベントが同時に発生するとキュー待ちが増大し、ポーリングより遅くなるケースもあるため注意が必要です。
これは後述するイベントストーム問題に直結します。

CPU利用率の比較

ポーリング方式のCPU利用率  U_{\text{poll}} は、イベントの有無にかかわらずほぼ一定です。

 

 U_{\text{poll}} = \dfrac{t_{\text{exec}}}{T_s}

 

これに対し、イベント駆動のCPU利用率  U_{\text{event}} はイベント発生率  \lambda に比例します。

 

 U_{\text{event}} = \lambda \cdot t_{\text{exec}}

 

λが小さい(イベントが稀にしか起きない)場合、イベント駆動のCPU利用率はポーリングに比べて大幅に低くなります。
具体的に数値で比較してみましょう。

Ts=10ms、texec=0.5ms、λ=10Hz(1秒に10回イベント発生)の場合を考えます。

 

Upoll = 0.5 / 10 = 5%

 

Uevent = 10 × 0.5 × 10-3 = 0.5%

 

イベント駆動の方がCPU利用率は10分の1です。
この差は消費電力の削減にも直結するため、バッテリー駆動のIoTセンサーノードなどで特に有利に働きます。

一方、 \lambda が大きくなり λ·texec > texec/Ts を超えると、イベント駆動の方がCPU負荷が高くなります。
この臨界点は λ > 1/Ts 、つまりイベント発生率がスキャン周波数を超えたときです。

 

3. イベント駆動の仕組み

イベント駆動がどのように動作するのかを、もう少し具体的に掘り下げます。
この章では、イベントループ・コールバック・イベントキューという3つの核心的な要素を解説します。

イベントループの役割

イベント駆動型システムの心臓部は「イベントループ」です。
ループは常時稼働しており、イベントキューに新しいイベントが入るのを待ち受けます。

イベントが到着すると、ループはキューから1つ取り出し、そのイベントに対応するハンドラ関数を呼び出します。
ハンドラの処理が完了すると、ループは再びキューを確認し、次のイベントを処理するか、空であれば待機状態に戻ります。

疑似コードで表現すると次のようになります。

while True:
    event = event_queue.wait()
    handler = lookup_handler(event.type)
    handler(event.data)

ここで重要なのは、event_queue.wait() が「ブロッキング待機」であるという点です。
キューが空の場合、CPUはスリープ状態に入り、新しいイベントが到着するまでリソースを消費しません。
これがポーリングの「ビジーウェイト」との根本的な違いです。

この構造により、プログラム全体の制御フローが「イベントの到着順」によって決まるという特徴が生まれます。
プログラマが明示的に制御フローを記述するのではなく、イベントの発生が制御フローを動的に形成するという「制御の反転(Inversion of Control)」が実現されています。

コールバック関数とハンドラ

コールバック関数とは、特定のイベントが発生したときに呼び出されるよう、あらかじめ登録しておく関数です。
「ボタンが押されたら関数Aを実行する」「温度が閾値を超えたら関数Bを実行する」のように、イベントの種類とハンドラを紐づけます。

この「登録」と「呼び出し」の分離が、イベント駆動のモジュール性を高めています。
新しいイベント種別を追加するときも、既存のコードを修正する必要がなく、新しいハンドラを登録するだけで済みます。
ソフトウェア工学の用語で言えば「開放閉鎖原則(Open-Closed Principle)」に自然と沿った設計になります。

ハンドラの登録パターンは大きく2つあります。

1つ目は直接登録パターンで、イベント種別とハンドラ関数を辞書(ハッシュマップ)で対応づける方式です。
シンプルで高速ですが、1つのイベントに1つのハンドラしか登録できないという制約があります。

2つ目はオブザーバパターンで、1つのイベントに複数のハンドラ(リスナー)を登録できる方式です。
イベントが発生すると、登録されたすべてのリスナーが順番に呼び出されます。
GUIフレームワークやメッセージングシステムで広く使われています。

ただし、コールバックが複雑にネストすると「コールバック地獄」と呼ばれる可読性の低下が起きやすい点には注意が必要です。
近年では、PromiseやAsync/Awaitパターンでこの問題を緩和する手法が広く使われています。

イベントキューの構造と待ち行列理論

イベントキューは、発生したイベントを一時的に蓄えるFIFO(First In, First Out)型のバッファです。
イベントの生成側(プロデューサ)と消費側(コンシューマ)を疎結合にする役割を果たします。

キューの深さDはシステム設計における重要なパラメータです。
イベントの発生率λと処理率μの関係から、定常状態でキューが溢れない条件は次のとおりです。

 

 \lambda < \mu = \dfrac{1}{t_{\text{exec}}}

 

この条件を満たさない場合、キューは無限に蓄積し、最終的にイベントの喪失(ドロップ)やシステムのフリーズにつながります。

待ち行列理論のM/M/1モデルを適用すると、キューの平均長  L_q とシステム内の平均滞在時間  W を推定できます。

 

 L_q = \dfrac{\rho^2}{1 - \rho}, \quad \rho = \dfrac{\lambda}{\mu}

 

 W = \dfrac{1}{\mu - \lambda}

 

ここでρはトラフィック強度と呼ばれ、ρ<1が安定条件です。
たとえばλ=80Hz、μ=100Hzのとき ρ=0.8であり、平均キュー長はLq=3.2、平均滞在時間はW=50msと計算されます。

ρが1に近づくとキュー長と滞在時間が急激に増大するため、実用上はρ≦0.7程度に抑えることが推奨されます。

同期処理と非同期処理

イベント駆動は「非同期処理」と密接に関わっています。

同期処理では、ある関数を呼び出すと結果が返るまで次の行に進めません。
これに対し非同期処理では、関数を呼び出した時点で即座に制御が戻り、結果は後からコールバックやPromiseで受け取ります。

イベント駆動ではイベントの発生タイミングが不定なため、本質的に非同期です。
この非同期性がシステムの並行性を高めますが、同時に「いつ、どの順序で処理が実行されるか」を予測しにくくする原因にもなります。

同期・非同期の選択はシステムの要件によって決まります。
リアルタイム制御のように「この処理は必ずこのタイミングで完了していなければならない」という要件がある場合は同期的なアプローチが適しています。
一方、「いつ完了するかは不定だが、完了したら次の処理を開始したい」という要件には非同期イベント駆動が適しています。

 

4. イベント駆動型アーキテクチャの構成要素

イベント駆動の考え方をシステム全体の設計に拡張したものが「イベント駆動型アーキテクチャ」(EDA: Event-Driven Architecture)です。
EDAは、マイクロサービスやIoTプラットフォームの設計パターンとして広く採用されています。

プロデューサとコンシューマ

EDAの基本登場人物は「プロデューサ」と「コンシューマ」の2つです。

プロデューサはイベントを生成する側です。
製造現場では、温度・圧力・振動センサー、バーコードリーダー、ビジョンシステム、PLCの出力信号などがプロデューサに該当します。
それぞれが自律的にイベントを発生させ、中央の管理者を必要としません。

コンシューマはイベントを受信して処理する側です。
データベースへの品質データ書き込み、異常発生時のアラート送信、MES(製造実行システム)への稼働データ連携、アクチュエータへの即時指令出力などを担当します。

両者は直接接続されず、イベントチャネル(ブローカ)を介してやり取りするのがEDAの特徴です。
これにより、プロデューサとコンシューマは互いの存在を知らなくても機能でき、「疎結合(Loose Coupling)」が実現されます。
あるセンサーが故障しても、他のコンシューマの動作には影響しません。

イベントブローカの役割

イベントブローカは、プロデューサが発行したイベントを受け取り、適切なコンシューマに配信するミドルウェアです。
Apache Kafka、RabbitMQ、AWS EventBridge、MQTTブローカなどが代表的な実装です。

ブローカの主な機能を整理すると次のとおりです。

  • イベントのルーティング:トピックやフィルタ条件に基づいてイベントを適切なコンシューマに振り分けます
  • 永続化:イベントをディスクに保存し、コンシューマがダウンしても後から再処理できるようにします
  • 順序保証:同一パーティション内のイベント順序を保証します
  • スケーリング:コンシューマの数を動的に増減させ、処理能力を調整します

ブローカのスループット  \Theta は、パーティション数Pと各パーティションの処理速度viの合計で表現できます。

 

 \Theta = \sum_{i=1}^{P} v_i

 

パーティション数を増やすほどスループットが向上しますが、パーティション間の順序保証は失われるため、設計上のトレードオフが存在します。
たとえば、ある製品の品質データは時系列順序が重要なため同一パーティションに集約する必要がありますが、異なる製品間では順序は不問のため別パーティションに分散できます。

Pub/Subモデルとイベントストリーミング

EDAの代表的な通信パターンとして「Pub/Sub(Publish/Subscribe)モデル」があります。

プロデューサがトピックに対してイベントを「発行(Publish)」し、そのトピックを「購読(Subscribe)」しているコンシューマ全員にイベントが配信されます。
1対多の通信が自然に実現できるため、複数のサブシステムを疎結合に連携させるのに適しています。

製造現場での具体例を挙げると、振動センサーが「異常振動検知」イベントを発行すると、保全管理システム(保全作業指示を生成)、MES(設備稼働状態を更新)、BIツール(ダッシュボードに反映)がそれぞれ独立に受信して処理するという構図です。
センサー側は「誰が受信するか」を知る必要がなく、新しいコンシューマ(たとえばAI予知保全サービス)を後から追加することも容易です。

さらに発展した形態として「イベントストリーミング」があります。
これはイベントをリアルタイムで連続的に処理するパターンで、Apache Kafkaがその代表です。

イベントストリーミングでは、過去のイベントも永続的に保存されるため、新しいコンシューマが後から追加されても、過去のイベントを再生して処理できます。
これは「イベントソーシング」と呼ばれるパターンの基盤にもなっており、任意の時点のシステム状態を復元できるという強力な特性を持ちます。

メディエータトポロジとブローカトポロジ

EDAの構成パターンは大きく2種類に分類されます。

ブローカトポロジは、中央のメディエータを持たず、イベントがブローカを通じてコンシューマに直接配信される構成です。
処理の流れがシンプルで高速ですが、複数ステップにまたがるワークフローの管理が難しくなります。
単純なイベント通知(センサーデータの収集や異常検知の通知など)に適しています。

メディエータトポロジは、中央にオーケストレータ(メディエータ)が存在し、イベントの処理順序やエラーハンドリングを制御する構成です。
「品質異常→原因分析→是正処置指示→効果確認」のような多段階ワークフローを確実に制御できますが、メディエータがボトルネックや単一障害点になるリスクがあります。

どちらを選ぶかは、システムの複雑度と要求される信頼性のバランスで判断します。
実務では両者を併用し、単純な通知はブローカトポロジ、複雑なワークフローはメディエータトポロジで処理するハイブリッド構成が多く見られます。

 

5. 組込みシステムにおけるイベント駆動

イベント駆動はWeb系の技術と思われがちですが、組込みシステムの世界でも古くから使われてきました。
その代表が「割り込み(Interrupt)」であり、ハードウェアレベルでのイベント駆動と言えます。

割り込みとイベント駆動の関係

割り込みとは、CPUが実行中のプログラムを一時停止し、優先度の高い処理(割り込みサービスルーチン:ISR)を即座に実行する仕組みです。

外部デバイスからの信号(外部割り込み)やタイマーの満了(内部割り込み)がトリガーとなる点で、イベント駆動の考え方と本質的に同じです。
違いは、割り込みがハードウェアレベルで実装され、ISRの実行がナノ秒〜マイクロ秒単位で保証される点にあります。

割り込みの応答時間  t_{\text{ISR}} は次の式で近似できます。

 

 t_{\text{ISR}} = t_{\text{latch}} + t_{\text{context}} + t_{\text{vector}} + t_{\text{exec\_ISR}}

 

ここで tlatchは割り込み要求のラッチ時間、tcontextはコンテキスト退避時間、tvectorはベクタテーブル参照時間、texec_ISRはISR本体の実行時間です。

典型的なARM Cortex-Mプロセッサでは、tlatch+tcontext+tvectorの合計が12クロックサイクル程度であり、100MHzで動作する場合は約120nsです。
これはソフトウェアイベントループの応答時間(マイクロ秒〜ミリ秒オーダー)よりも桁違いに高速です。

関連記事

instant.engineer

RTOSにおけるイベント駆動

リアルタイムOS(RTOS)は、割り込みをベースにしつつ、ソフトウェアレベルでのイベント駆動機構を提供します。

RTOSのタスクは、セマフォやイベントフラグの「待ち状態」に入ることで、CPUリソースを他のタスクに譲ります。
ISRが割り込みを処理した後、セマフォを解放すると、待機していたタスクが起床(ウェイクアップ)して処理を開始します。

この「ISR→セマフォ解放→タスク起床」という流れは、割り込みとソフトウェアイベント駆動のハイブリッドモデルです。
ISRの中では最小限の処理(フラグセットやセマフォ解放)だけを行い、時間のかかる処理はタスク側で実行するという「遅延処理(Deferred Processing)」パターンが標準的な設計手法です。

RTOSにおける最悪応答時間  t_{\text{WCRT}} は次のように見積もります。

 

 t_{\text{WCRT}} = t_{\text{ISR}} + t_{\text{sched}} + t_{\text{preempt}} + t_{\text{task}}

 

ここで tschedはスケジューラのオーバーヘッド、tpreemptはプリエンプション遅延、ttaskはタスク本体の実行時間です。
プリエンプティブRTOSでは、高優先度タスクが低優先度タスクを中断して実行されるため、最悪応答時間の予測が可能です。

代表的なRTOSであるFreeRTOSでは、xSemaphoreGiveFromISR()やxQueueSendFromISR()といったAPIにより、ISRからタスクへのイベント通知が安全に行えます。
μITRONやVxWorksなど、産業用RTOSでも同様の機構が提供されています。

関連記事

instant.engineer

ベアメタル環境でのイベント駆動

RTOSを使わないベアメタル環境でも、イベント駆動のアプローチは可能です。

最も基本的な手法は「スーパーループ+フラグ」パターンです。
ISR内でフラグ変数をセットし、メインループ(スーパーループ)がフラグを確認して処理を実行します。

volatile bool sensor_event = false;

void ISR_Sensor(void) {
    sensor_event = true;
}

int main(void) {
    while (1) {
        if (sensor_event) {
            sensor_event = false;
            handle_sensor();
        }
    }
}

この方式はポーリングとイベント駆動の中間的な位置づけです。
ISRによるイベント検知は即座に行われますが、実際の処理はメインループのスキャン周期に依存するため、厳密なリアルタイム性は保証されません。

より洗練された手法としては、アクティブオブジェクトパターンやステートマシンフレームワーク(QP/Cなど)があります。
QP/Cフレームワークでは、各アクティブオブジェクトが専用のイベントキューを持ち、状態マシン(ステートマシン)に基づいてイベントを処理します。
これにより、ベアメタル環境でも完全なイベント駆動アーキテクチャを実現でき、RTOSのオーバーヘッドなしに構造化されたイベント処理が可能になります。

 

6. PLCとイベント駆動

製造現場で最も身近な制御装置であるPLC(Programmable Logic Controller)は、典型的な周期処理型のシステムです。
ここでは、PLCのスキャンサイクルとイベント駆動の関係を整理します。

PLCのスキャンサイクル

PLCの基本動作は「スキャンサイクル」と呼ばれ、次の3ステップを繰り返します。

  • 入力スキャン:全入力端子の状態を一括で読み取り、入力イメージテーブルに格納します
  • プログラム実行:ラダープログラムを先頭から末尾まで逐次実行します
  • 出力更新:演算結果を出力イメージテーブルから物理出力端子に一括で書き出します

スキャンタイムTscanは入力スキャン時間tin、プログラム実行時間tprog、出力更新時間toutの合計です。

 

 T_{\text{scan}} = t_{\text{in}} + t_{\text{prog}} + t_{\text{out}}

 

一般的なPLCではTscanは数ms〜数十msの範囲です。
この周期は固定であり、プログラムの処理量が増えるとスキャンタイムも延びます。

スキャンサイクル方式の最大の利点は決定論的な動作です。
同じ入力に対して常に同じ出力が得られ、タイミングも予測可能です。
これは安全性が最優先される産業制御において極めて重要な特性です。

関連記事

instant.engineer

PLCにおけるイベント的な機能

PLCは基本的に周期処理ですが、完全にイベント駆動と無縁というわけではありません。
多くのPLCは、スキャンサイクルとは独立に動作する「割り込みタスク」を備えています。

代表的な割り込みタスクとして、外部入力の立ち上がりエッジで起動する「外部割り込みタスク」があります。
高速カウンタの一致で起動する「カウンタ割り込み」も広く使われており、エンコーダの特定パルス数到達時にアクションを実行する用途に適しています。
さらに、メインのスキャンサイクルより短い間隔で実行する「定周期割り込みタスク」により、高速な制御ループを実現することもできます。

これらはスキャンサイクルを中断して即座に実行されるため、イベント駆動的な即時応答を実現します。
ただし、使用できる割り込みの種類や数はPLCの機種に依存し、一般的なプログラミングモデルには含まれない「特殊機能」として扱われます。

たとえば三菱電機のMELSEC iQ-Rシリーズでは、最大32点の外部割り込み入力と、最小0.5msの定周期割り込みタスクが利用可能です。
オムロンのNXシリーズでは、プライオリティ16段階のタスク管理により、イベント駆動的な高優先度処理を柔軟に構成できます。

IEC 61131-3とIEC 61499

PLCの国際標準であるIEC 61131-3では、5つのプログラミング言語(LD, FBD, ST, IL, SFC)が規定されています。

このうちSFC(Sequential Function Chart)は、条件(トランジション)が成立したときに次のステップへ遷移するという「条件駆動型」の動作モデルです。
トランジション条件はセンサー入力やタイマー完了などのイベントに相当するため、SFCは周期処理の枠組みの中にイベント駆動的な考え方を取り込んだ記法と解釈できます。

さらに重要なのがIEC 61499規格です。
この規格は分散制御システムにおけるイベント駆動設計を標準化するために策定されました。
ファンクションブロックにイベント入出力を明示的に定義でき、イベントの到着がファンクションブロックの実行をトリガーします。

IEC 61131-3がスキャンサイクルベースの集中制御を前提としているのに対し、IEC 61499はイベントベースの分散制御を前提としている点が根本的な違いです。
IoTやIndustry 4.0の進展に伴い、IEC 61499への関心は高まっていますが、現時点ではIEC 61131-3が圧倒的に普及しており、移行は緩やかです。

スキャンサイクルの限界とイベント駆動の補完

PLCのスキャンサイクルは決定論的で信頼性が高い反面、入出力の点数やプログラム量が増大するとスキャンタイムが延びるという構造的な限界があります。

応答遅延の最悪値  t_{\text{worst}} は、入力変化がスキャン直後に発生した場合を考えると次のように表せます。

 

 t_{\text{worst}} = 2 \cdot T_{\text{scan}} + t_{\text{in}} + t_{\text{out}}

 

入力イメージの更新タイミングと出力の書き出しタイミングの両方でスキャン1周期分のずれが生じるため、最悪2スキャン分の遅延が発生します。

具体例で計算すると、Tscan=20ms、tin=1ms、tout=1msのとき、最悪応答遅延は42msです。
高速なパーツフィーダーの異常検知や、衝突回避のための緊急停止のように数ms以内の応答が求められるケースでは、この遅延は許容できません。

このようなケースでは、割り込みタスクやイベント駆動型のエッジコントローラで補完する設計が合理的です。
PLCが全体の定常制御を担い、エッジコントローラが例外的な高速イベントを処理するという役割分担は、現代の製造ラインにおいてますます一般的になっています。

 

7. イベント駆動の設計指針と注意点

イベント駆動は強力なアーキテクチャですが、万能ではありません。
この章では、イベント駆動を採用する際に考慮すべき設計指針と、よくある落とし穴を整理します。

設計指針:いつイベント駆動を選ぶか

イベント駆動が特に有効な場面は、以下の条件が重なるケースです。

  • イベントの発生頻度が不定で、長い休止期間がある(例:異常検知、ユーザー操作)
  • 応答のリアルタイム性が求められるが、決定論的でなくてよい(例:アラート通知)
  • システムのコンポーネント同士を疎結合に保ちたい(例:マイクロサービス連携)
  • 将来的な拡張が見込まれる(例:IoTセンサーの追加、新しい分析サービスの接続)

逆に、一定周期で確実にすべての入出力を処理する必要がある場合(モーションコントロール、閉ループ制御など)や、処理の順序と実行タイミングが厳密に決定論的であることが求められる場合(安全制御、SIL認証が必要なシステム)は、周期処理の方が適しています。

実務では、システム全体を純粋にどちらか一方で構成するよりも、「基盤は周期処理、例外的な高速応答はイベント駆動」というハイブリッド設計が最も多く採用されています。
このハイブリッドアプローチにより、周期処理の決定論性とイベント駆動の効率性・柔軟性を両立できます。

デバッグの困難さとその対策

イベント駆動の最大の課題は、デバッグと再現性の確保です。

イベントの発生タイミングは外部要因に依存するため、同じ条件を再現することが難しくなります。
あるバグが「特定のイベントが特定の順序で発生したときだけ」起きるとすると、従来のステップ実行型デバッガでは追跡が困難です。
この種のバグは「ハイゼンバグ」と呼ばれ、観測しようとすると消えてしまうことがあります。

対策として、すべてのイベントにタイムスタンプ付きのログを残す「イベントトレーシング」が有効です。
ログにはイベントの種類、発生時刻、発生元、付随データを記録し、事後的に時系列で分析できるようにします。

さらに強力な手法として「イベントリプレイ」があります。
記録したイベントログを再生してシステムの挙動を再現する技術で、分散システムのデバッグにおいて不可欠なツールです。
イベントソーシングパターンを採用していれば、任意の時点の状態を完全に復元できるため、バグの原因特定が飛躍的に容易になります。

競合状態と排他制御

複数のイベントハンドラが共有リソース(変数、ファイル、データベースなど)に同時にアクセスすると、競合状態(レースコンディション)が発生するリスクがあります。

シングルスレッドのイベントループ(Node.js方式)であれば、同時に1つのハンドラしか動かないため競合は起きません。
しかし、マルチスレッドや複数プロセスでイベントを処理する場合は、ミューテックスやセマフォによる排他制御が不可欠です。

排他制御のオーバーヘッド  t_{\text{lock}} は、ハンドラの実行時間に加算されるため、イベントの処理スループットに影響します。

 

 t_{\text{exec\_eff}} = t_{\text{exec}} + t_{\text{lock}}

 

ロックの粒度を細かくするか、ロックフリーのデータ構造(CAS命令ベースのキューなど)を使うことで、この影響を最小限に抑えることが重要です。
組込みシステムでは、割り込み禁止区間を最短にすることが同様の原則として適用されます。

イベントストーム(バースト)対策

短時間に大量のイベントが発生する「イベントストーム」は、キューの溢れやシステムの応答遅延を引き起こす深刻な問題です。
製造現場では、設備の異常時にセンサーが一斉にアラートを上げるケースがこれに該当します。

対策としては3つの手法が有効です。

1つ目はデバウンスで、一定時間内の重複イベントを間引く手法です。
チャタリングの除去と同じ原理で、同一種別のイベントが連続発生した場合に最初(または最後)のみを処理します。

2つ目はスロットリングで、単位時間あたりの処理数に上限を設ける手法です。
上限レート  R_{\text{max}} は次のように設計します。

 

 R_{\text{max}} = \dfrac{1}{t_{\text{exec\_eff}}} \cdot \alpha, \quad 0 < \alpha < 1

 

ここでαは安全係数であり、通常は0.7〜0.9程度に設定して余裕を持たせます。

3つ目はバックプレッシャーで、コンシューマがプロデューサに「送信を遅らせて」と通知するフロー制御です。
TCP/IPの輻輳制御と同じ原理であり、システム全体の安定性を維持するための根本的な対策です。

テスト戦略

イベント駆動システムのテストでは、個々のハンドラを単独でテストする「ユニットテスト」に加えて、イベントの発生順序と組み合わせを網羅する「シナリオテスト」が重要になります。

特に注意すべきテストケースとして、同一イベントの連続発生(バースト耐性)、異なるイベントの同時発生(競合検出)、イベントの欠落(タイムアウト処理の確認)、イベントの順序逆転(順序依存バグの検出)があります。

さらに、異常系のイベント(タイムアウト、キュー溢れ、不正なイベントデータ)に対するハンドリングも忘れてはなりません。
フィードバック制御のように、出力を入力にフィードバックするループ構造がイベント駆動の中に存在する場合は、無限ループや発振の可能性も検証する必要があります。

プロパティベーステスト(QuickCheckやHypothesisなど)を活用して、ランダムなイベント列に対するシステムの不変条件を検証する手法も、イベント駆動システムのテストに有効です。

産業システムにおける設計パターン

製造現場でイベント駆動を導入する際の実践的な設計パターンをいくつか紹介します。

1つ目は「周期処理+イベント割り込み」パターンです。
PLCが定常制御(モーション制御、温度調節など)を周期処理で実行しつつ、エッジコントローラが異常検知や品質イベントを処理します。
このパターンでは、PLCの決定論的な動作が安全性を担保し、エッジコントローラのイベント駆動が柔軟性と即応性を提供します。

2つ目は「ゲートウェイ集約」パターンです。
複数のセンサーやPLCからのイベントをIoTゲートウェイで集約し、フィルタリングや前処理を行った上でクラウドやオンプレミスのサーバーに送信します。
ゲートウェイでのエッジ処理により、ネットワーク帯域の節約と応答時間の短縮を両立できます。

3つ目は「CQRS+イベントソーシング」パターンです。
CQRS(Command Query Responsibility Segregation)は、データの書き込み(コマンド)と読み取り(クエリ)を分離するアーキテクチャパターンです。
イベントソーシングと組み合わせることで、すべての状態変化をイベントとして記録し、任意の時点の状態を復元できます。
品質トレーサビリティが厳格に求められる自動車・航空宇宙・医療機器の製造分野で、このパターンの採用が増えています。

 

8. まとめ

本記事では、イベント駆動の基本概念から周期処理との違い、アーキテクチャの構成要素、組込みシステムやPLCとの関係、そして設計上の注意点までを解説しました。

イベント駆動とは「イベントの発生をトリガーとして処理を実行する」動作モデルであり、周期処理(ポーリング)が「時間の経過」で処理を起動するのとは根本的に異なるアプローチです。

CPUリソースの効率的な利用、高いスケーラビリティ、コンポーネント間の疎結合といった利点がある一方で、デバッグの困難さ、競合状態のリスク、イベントストームへの脆弱性といった課題も存在します。

組込みシステムでは割り込みというハードウェアレベルのイベント駆動が古くから活用されており、PLCにおいても割り込みタスクやSFCという形でイベント駆動的な要素が取り入れられています。
IEC 61499規格の登場により、PLCの世界にもイベント駆動設計が標準的に持ち込まれつつあります。

実務で最も効果的なのは、周期処理とイベント駆動を適材適所で組み合わせるハイブリッド設計です。
「すべての処理を一定周期で回す」時代から、「変化が起きたときに即応する」仕組みを適切に織り込む設計へ。
その判断力を養うことが、これからの制御システム設計者に求められる重要なスキルです。

PID制御やフィードバック制御のような古典制御理論と、イベント駆動のような現代的なソフトウェアアーキテクチャの両方を理解することで、より柔軟で堅牢なシステム設計が可能になります。