別のスレッドで仕事が増えたことを、待機中の処理へ伝えたい場合があります。Linuxのeventfdは、そうした通知をファイルディスクリプターとして扱う仕組みです。ただし、書き込んだ内容を順番に保存するメッセージキューではありません。中にあるのは一つのカウンターで、複数の通知を足し合わせて受け取ることができます。
簡単にまとめると?
- 通常のeventfdでは、書き込んだ整数が加算され、読み取り時に合計が返ってカウンターが0になります。
- EFD_SEMAPHOREを指定すると、読み取りごとに値1を返し、カウンターを1減らします。書込みの区切りが復元されるわけではありません。
- カウンター値は書込み回数とは限りません。仕事の内容や順序、失敗・完了の状態は別のデータ構造で管理します。
足されるのは、呼び出し回数ではなく整数の値
eventfdを作ると、カーネル内に符号なし64ビット整数のカウンターができます。作成時のinitvalが初期値です。返されたファイルディスクリプターを通じて、readとwriteでそのカウンターを扱います。
初期値0に対し、値1、2、3を順に書き込んだとします。途中に読み取りがなければ、カウンターは1、3、6と増えます。書込みは3回ですが、値は6です。「1回の通知で必ず1を書く」というアプリケーション側の約束があって初めて、値と通知件数が一致します。
その値が何を数えているかも、eventfd自身は知りません。未処理の仕事数、まとめて追加された項目数、単に目を覚ます必要があるという合図など、意味は利用側が決めます。ファイルディスクリプターが読めるようになったことを、仕事が正常に終わった証明として使うこともできません。
通常モードとセマフォモードで、減り方が違う
EFD_SEMAPHOREを指定しない通常モードでは、カウンターが0でないときに読み取ると、その値を受け取り、カウンターは0へ戻ります。上の例なら、読み取りで6を受け取ります。値1、2、3という三つの記録が順に出てくるのではありません。
EFD_SEMAPHOREを指定した場合は、カウンターが0でない限り、一回の読み取りで値1を受け取り、内部の値が1だけ減ります。6がたまっていれば、ほかの書込みや読取りがない条件で、1を6回取り出せます。このモードも、元の三つの書込みを再現する仕組みではありません。

読み取りで受け取る整数の値と、readの戻り値にも注意が必要です。成功したreadは8バイトを返し、その8バイトの中にカウンター値または1が入っています。たとえば「readが8を返した」ことを、通知が8件あったことと混同してはいけません。マニュアルでは、8バイトより小さい読取り領域はEINVALとなり、整数はホストのバイト順で渡されるとされています。
0は終わりではなく、今は蓄積がない状態
カウンターが0のとき、通常のブロッキング読み取りは、値が増えるまで待ちます。EFD_NONBLOCKなどで非ブロッキングにしてあれば、EAGAINを返します。パイプのEOFのように「送り手がすべて閉じた」と知らせる意味ではありません。
終了を通知したいなら、終了状態を別に保存してから待機中の処理を起こす、といったアプリケーションの規約が必要です。同じeventfdを共有する読み手が複数いる場合は、読み取りによる減算も同じカウンターへ作用します。ある読み手が6を受け取った後に、別の読み手も6を受け取れるとは限りません。
pollやepollでは、カウンターが0より大きいと読み取り可能として扱われます。ソケットからの入力と、別スレッドからの通知を、同じ待機の仕組みにまとめられるのが利点です。ただし、epollが何回イベントを返すかと、eventfdに加えた値の合計は別に扱います。
カウンターには上限があり、書込みも失敗し得る
ユーザー空間からの通常のwriteで保持できる最大値は、符号なし64ビット整数の最大値から1を引いた値、つまり264−2です。加算するとその値を超える場合、ブロッキングなら読み取りを待ち、非ブロッキングならEAGAINとなります。上限を超えて自動的に0へ戻るカウンターとして使うことはできません。
また、264−1という値そのものを書き込むことはEINVALになります。実装では、writeが成功したかを確かめずに「通知済み」と記録しないことが重要です。この説明はユーザー空間からの通常の読み書きを対象とし、マニュアルにあるカーネル非同期I/Oによる特殊なオーバーフロー状態は含めていません。
通知と仕事本体を、別々に管理する
たとえば、共有キューに画像処理の仕事を追加し、eventfdで担当スレッドを起こす構成を考えます。画像の場所や処理パラメーターはキュー側にあり、eventfdはキューを調べるきっかけを渡します。どの仕事を誰が取り出したか、いつ終わったか、失敗した仕事を戻すかは、キューと状態管理の役割です。
キューへの追加と通知の順序、複数スレッドが触るデータの同期、通知を受け取ってから再び待つまでの競合も設計対象になります。eventfdを使ったというだけで、共有メモリーの更新がすべて正しく同期されるとはみなしません。ロックや適切な原子操作など、キューに合った同期方式を合わせます。
数を集約した合図だけで足りるならeventfdは候補になります。送り手ごとの情報やメッセージの境界が必要なら、別のキューや通信方式が必要です。選ぶ前に「合計だけが分かればよいか」「一件ずつの内容や順番を残す必要があるか」を決めると、通常モードとセマフォモードの違いも使い分けやすくなります。
この記事はLinux man-pages 6.19のeventfdの説明と、独自の整数カウンターモデルに基づきます。図はカウンターの加減算を検査したもので、Linuxカーネル上の負荷試験や、特定の並行キューの正しさを実証したものではありません。
出典
- Linux man-pages 6.19 — eventfd(2):DESCRIPTIONのread/writeとpoll/select、NOTES、EXAMPLES。
