libevによる非同期プログラミング


libev

libevは非同期処理を扱うライブラリでnode.jsなどでも使用されている。
もともとUnix系にはselect()というファイルディスクリプタを扱う非同期処理があったが、これは監視できるファイルディスクリプタの数に制限があった。その後に制限のないpoll()が開発され、さらにLinuxではpoll()を高速化したepoll()が開発され、BSDではKQUEUEが開発された。そしてこれらの違いを吸収するためにlibevというライブラリが開発された。

インストール

brew install libev

現在のバージョンが次の場所にインストールされる。
このエントリではライブラリパスを通さずにこのパスをそのまま扱います。

/usr/local/Cellar/libev/4.15/

xcodeの設定

プロジェクトのBuild Settingsの「Header Search Paths」に以下を追加する。

/usr/local/Cellar/libev/4.15/include/

プロジェクトのBuild Settingsの「Library Search Paths」に以下を追加する。

/usr/local/Cellar/libev/4.15/lib/

プロジェクトのBuild Settingsの「Linking」に以下を追加する。

-lev
-ObjC
-all_load

基本的な使い方

まずイベントループオブジェクトを宣言します。
次に監視したいタイプのイベントの監視オブジェクトを宣言ししてコールバックとファイルディスクリプタを関連付けます。最後にイベントループと監視オブジェクトを関連付けると同時にループを開始します。

以下は公式のサンプルを少し変更してます。

#include <ev.h>
#include <iostream>

static void stdin_cb (EV_P_ ev_io *w, int revents)
{
    std::cout << "stdin ready\n";

    // I/Oとループの関連づけを解除.
    ev_io_stop (EV_A_ w);

    // イベントループの解除.
    ev_unloop (EV_A_ EVUNLOOP_ALL);
}

static void timeout_cb (EV_P_ ev_timer *w, int revents)
{
    std::cout << "timeout\n";

    // タイマーをストップする.
    ev_timer_stop (EV_A_ w);

    // イベントループの解除.
    ev_unloop (EV_A_ EVUNLOOP_ONE);
}

int main(int argc, const char * argv[])
{
    // イベントループを作成する.
    struct ev_loop *loop = ev_loop_new (0);

    // I/Oの監視オブジェクト.
    ev_io stdin_watcher;

    // I/Oの監視オブジェクトに対して、
    // コールバックとFDとイベントのタイプを関連付ける.
    ev_io_init (&stdin_watcher, stdin_cb, /*STDIN_FILENO*/ 0, EV_READ);

    // I/Oの監視オブジェクトとイベントループを関連付ける.
    ev_io_start (loop, &stdin_watcher);


    // タイマーオブジェクトを作成する.
    ev_timer timeout_watcher;

    // タイマーオブジェクトにコールバックを関連づけ、タイムアウトを指定する(5秒で繰り返し0).
    ev_timer_init (&timeout_watcher, timeout_cb, 5, 0.);

    // タイマーオブジェクとイベントループを関連付ける.
    ev_timer_start (loop, &timeout_watcher);

    // ループ開始.
    ev_loop (loop, 0);

    std::cout << "end¥n";

    // 作成したイベントループの破棄
    ev_loop_destroy(loop);

    return 0;
}

xcodeだとコマンドラインで対話できないので、上の例は次の方法でgccでビルドして試します。

# gcc -o test test.cpp -lev -I/usr/local/Cellar/libev/4.15/include/ -L/usr/local/Cellar/libev/4.15/lib/ -lstdc++

イベントループの作成

公式のサンプルではev_default_loop()が使われていますが、これはスレッドセーフではないようなので、上記の例ではev_loop_new(0)でイベントループを作成して、ev_loop_destroy (loop)で破棄しています。

このイベントループの作成でどの非同期処理を使用するかを引数で指定します。デフォルトは0で自動で設定されます。

libevが使用している非同期の技術

次のメソッドでバックエンドで何が使用されているのか確認できます。

unsigned int ev_supported_backends ()
  EVBACKEND_SELECT  = 0x00000001U, /* about anywhere */
  EVBACKEND_POLL    = 0x00000002U, /* !win */
  EVBACKEND_EPOLL   = 0x00000004U, /* linux */
  EVBACKEND_KQUEUE  = 0x00000008U, /* bsd */
  EVBACKEND_DEVPOLL = 0x00000010U, /* solaris 8 */ /* NYI */
  EVBACKEND_PORT    = 0x00000020U, /* solaris 10 */
  EVBACKEND_ALL     = 0x0000003FU, /* all known backends */
  EVBACKEND_MASK    = 0x0000FFFFU  /* all future backends */

次のメソッドで推奨?の非同期処理を取得できる。

unsigned int ev_recommended_backends();

監視できるイベント

これらは監視オブジェクトの種類によって扱えるものが決まります。

EV_READ
EV_WRITE
EV_TIMEOUT
EV_PERIODIC
EV_SIGNAL
EV_CHILD
EV_STAT
EV_IDLE
EV_PREPARE
EV_CHECK
EV_EMBED
EV_FORK
EV_ASYNC
EV_ERROR

監視オブジェクトのタイプ

ev_io
ev_timer
ev_periodic
ev_signal
ev_stat
ev_idle
ev_embed
ev_fork
ev_async

次回はソケットを扱ってみようと思います。