イベントループ

出典: フリー百科事典『ウィキペディア(Wikipedia)』
メインループから転送)
移動: 案内検索

イベントループ (event loop)、メッセージディスパッチャ (message dispather)、メッセージループ (message loop)、メッセージポンプ (message pump)、ランループ (run loop) とは、プログラム内でイベントメッセージを待ちうけ、それらをディスパッチする構成要素である。内部または外部の「イベントプロバイダー」(通常、イベントが到着するまで要求をブロックする)に要求することで作動し、次いで適当なイベントハンドラ英語版を呼び出す(イベントのディスパッチ)。イベントプロバイダがファイルインタフェースに従う場合、イベントループは reactor英語版 と連携する形で使われることがあり、select() または poll() を使ってファイルインタフェースにアクセスする。イベントループはほぼ常にメッセージの発信元とは非同期に動作する。

イベントループはプログラムの中心的制御構造となっていることが多い。そのためそれをメインループ (main loop) またはメインイベントループ (main event loop) とも呼ぶ。そのようなプログラムではイベントループが最上位の制御構造となっており、そのため「メイン」と名づけられている。

メッセージパッシング[編集]

メッセージポンプという呼称は、プログラムのメッセージキュー(通常OSが割り当て、OSが所有する)からメッセージを汲み上げ、そのプログラム内で処理することに由来する。厳密には、イベントループはプロセス間通信の実装法の1つである。実のところメッセージ処理は多くのシステムに存在し、例えばMachカーネルレベルのコンポーネントにもある。イベントループはメッセージを使用するシステムの実装技法の1つである。

代替手法[編集]

この手法は以下のような他の手法とは対照的である:

  • 古くからある、単純に一回動作して終了するプログラム。この種のプログラムは情報処理の最初期からあり、ユーザーとの対話手段を持たない。現在も主にCUI指向のプログラムでよく使われている。各種パラメータを指定して起動される。
  • メニュー駆動型設計。この場合も一種のメインループは存在するが、ユーザーから見てイベント駆動的ではない。イベント駆動の代わりとして、階層型のメニューを順次選択していって、希望する動作を指定する。このメニューを通した限定的な対話性がある。

[編集]

GUIインタフェースが主流となったため、多くのアプリケーションがメインループを持つようになった。get_next_message() というルーチンは一般にOSが提供するもので、メッセージが到着するまでブロックされる。したがってこのループが動作するのは処理すべきものが存在する場合だけである。

function main
    initialize()
    while message != quit
        message := get_next_message()
        process_message(message)
    end while
end function

ファイルインタフェース[編集]

UNIXでは「あらゆるものはファイルである」というパラダイムにより、ファイルベースのイベントループが自然に生まれた。ファイルの読み書きだけなく、プロセス間通信、ネットワーク通信、デバイス制御が全てファイルI/Oで行われ、対象はファイル記述子で指定される。select および poll システムコールを使えば、複数のファイル記述子の状態変化を同時に監視でき、読み込むべきデータが到着したことを検知できる。

例として、継続的に更新されるファイルから読み込んでその内容を X Window System に表示するプログラムを示す。クライアントとはソケットを通じて通信する。

main():     
    file_fd = open ("logfile")
    x_fd = open_display ()
    construct_interface ()
    while changed_fds = select ({file_fd, x_fd}):
        if file_fd in changed_fds:
            data = read_from (file_fd)
            append_to_display (data)
            send_repaint_message ()
        if x_fd in changed_fds:
            process_x_messages ()

シグナル処理[編集]

UNIXでファイルインタフェースに従わない数少ない例として、非同期イベント(シグナル)がある。シグナルはシグナルハンドラで受信する。シグナルハンドラは小さな制限されたコードであり、それが動作中はプログラム本体の処理はサスペンドされる。select() でブロック中にシグナルを受信して処理した場合、select はEINTRというエラーコードを伴って早期に戻る。プログラムがCPUを使用している間にシグナルを受信すると、シグナルハンドラを実行する間は本体の実行がサスペンドされる。

したがってシグナルを考慮するには、シグナルハンドラで大域変数のフラグをセットし、イベントループの select() 呼び出しの直前と直後でそのフラグをチェックすればよい。フラグがセットされていたら、ファイル記述子でのイベントと同様にシグナルを処理する。しかしながら、この技法では競合状態が生じる。フラグのチェックとselect()呼び出しの間にシグナルが到着した場合、select() が他の理由で戻るまでシグナルを処理できない。

この問題を解決するため、POSIXでは pselect システムコールを提供している。これは select に似ているが sigmask という引数が追加されており、シグナルのマスクを設定できる。これを使えば、普段はシグナルをマスクしておき、select を呼び出している間だけマスクを解除することができる。すると、シグナルは select がイベントを待ち受けている間だけ受信されることになる。ただし、pselect() が利用可能となったのは比較的最近のことで、Linuxカーネルの 2.6.16 より以前の版では pselect() システムコールは実装されておらず、glibcで競合状態の問題をはらんだ実装がなされていた。

より汎用的な代替技法として、非同期イベントを self-pipe trick と呼ばれる技法でファイルベースのイベントに変換してやる技法がある[1]。これはシグナルハンドラでパイプに1バイトを書き込み、そのパイプのもう一方の端を主プログラムが select で監視するという技法である[2]。Linuxカーネル 2.6.22 では、signalfd() という新システムコールが追加された。これはシグナル受信用の特別なファイル記述子を生成する。

実装例[編集]

Windowsアプリケーション[編集]

Microsoft Windows にてユーザーとやりとりするプロセスを動作させる場合、イベントに応答するためのメッセージループが必須である。Windows ではイベントとメッセージは同等視される。イベントとしては、ユーザーとのやりとり、ネットワークのトラフィック、システム処理、タイマー、プロセス間通信などがある。対話型でないI/Oのみのイベントについては、I/O完了ポート英語版がある。I/O完了ポートのループはメッセージループとは別に動作し、メッセージループと相互作用することがない。

大抵のWin32アプリケーションの「心臓部」は WinMain[3] 関数であり、ループ内で GetMessage() を呼び出す。GetMessage はメッセージまたは「イベント」を受信するまでブロックする。何らかの選択的処理の後 DispatchMessage() を呼び出し、対応するハンドラ (WindowProc) にメッセージをディスパッチする。専用の WindowProc のないメッセージは DefWindowProc というデフォルトのハンドラにディスパッチする。DispatchMessage はメッセージのHWNDハンドル(RegisterClassで登録)に対応するWindowProcを呼び出す。

メッセージの順序性[編集]

より最近の Windows では、システムや周辺機器が受信した順序でメッセージループにメッセージが到達することを保証している。これはマルチスレッドアプリケーションの設計で必須となる。

ただし、一部のメッセージには異なる規則が適用され、常に最後に受信されるメッセージや文書化された特別な優先順位で受信されるメッセージがある[4]

Xlib のイベントループ[編集]

Xlibを直接使用するXアプリケーションは、XNextEventファミリの関数を中心に構築される。XNextEventはイベントキューにイベントが到着するまでブロックし、イベントを受け取ると即座にアプリケーションが適切にそれを処理する。Xlibのイベントループが扱うのはウィンドウシステムのイベントのみである。他のファイルやデバイスについても待ち受ける必要がある場合は ConnectionNumber などのプリミティブからイベントループを自前で構築する必要があるが、一般にマルチスレッドを採用することが多い。

Xlibを直接使用するプログラムは少ない。より一般的にはXlib上に構築されたGUIツールキットを使用する。例えば、X Toolkit Intrinsics (Xt) 上のツールキットでは XtAppAddInput()XtAppAddTimeout() を使用する。

なお、シグナル受信時の状態が不定であるため、シグナルハンドラからXlib関数を呼び出すのは危険である[5]

GLib のイベントループ[編集]

GLibのイベントループはGTK+向けに作られたが、今ではD-BusなどのGUI以外のアプリケーションでも使われている。ファイル記述子群で監視したいリソースを指定する。シグナルを受信するかタイムアウトすると、ブロック状態から復帰する。GLibはファイル記述子と子プロセス終了のイベントを組み込みでサポートしているが、prepare-check-dispatch モデルの対象として任意のイベントを加えることが可能である[6]

GLibのイベントループを使っているアプリケーションライブラリとしては、GStreamerGnomeVFS非同期IOメソッド群があるが、最大のクライアントライブラリはGTK+である。ウィンドウシステムからのイベント(Xの場合、Xのソケットからの読み込み)はGTK+イベントに変換され、アプリケーションのウィジェットオブジェクト上のGLibシグナルとして発せられる。

OS X のループ[編集]

OS X では、スレッド毎に1つの CFRunLoop があり、それに任意個のソース(イベント源)とオブザーバ(ハンドラ)を対応させることができる。そのループがメッセージのキューイングとディスパッチを行い、それを通してソースとオブザーバがやりとりする。

Cocoaでは CFRunLoop が NSRunLoop に抽象化されており、任意のメッセージをキューイングして任意のオブジェクトにディスパッチできる。

脚注[編集]

  1. ^ D. J. Bernstein. “The self-pipe trick”. 2013-023-05閲覧。
  2. ^ BUGS, pselect(2): synchronous I/O multiplexing – Linux System Calls Manual (en)
  3. ^ WinMain
  4. ^ GetMessage() function with message priority list.
  5. ^ X11R5、X11R6、Xtでの対処法は Chapter: 26 Signal Handling
  6. ^ The Main Event Loop - Customizing the main loop iteration Gnome Dev Center

関連項目[編集]

外部リンク[編集]