1

私の C++ プログラムでは、lio_listio呼び出しを使用して、一度に多数 (最大数百) の書き込み要求を送信します。その後、いくつかの計算を行います。完了したら、次のバッチのリクエストを送信する前に、すべての未処理のリクエストが終了するのを待つ必要があります。これどうやってするの?

今、私は呼び出しaio_suspendごとに 1 つの要求をループで呼び出しているだけですが、これは醜いようです。struct sigevent *sevpへの引数を使用する必要があるようlio_listioです。私の現在の推測では、次のようにする必要があります。

  • メイン スレッドで、mutex を作成し、 への呼び出しの直前にロックしlio_listioます。
  • への呼び出しでlio_listio、このミューテックスをロック解除する通知関数/シグナル ハンドラを指定します。

これで目的の動作が得られるはずですが、確実に機能しますか? シグナルハンドラコンテキストからミューテックスを操作することは許可されていますか? 同じスレッドから再度ロックしようとしたり、別のスレッドからロックを解除しようとすると、pthread ミューテックスがエラー検出を提供して失敗する可能性があることを読みましたが、この解決策はデッドロックに依存しています。

シグナルハンドラを使用したコード例:

void notify(int, siginfo_t *info, void *) {
    pthread_mutex_unlock((pthread_mutex_t *) info->si_value);
}

void output() {
    pthread_mutex_t iomutex = PTHREAD_MUTEX_INITIALIZER;

    struct sigaction act;
    memset(&act, 0, sizeof(struct sigaction));
    act.sa_sigaction = &notify;
    act.sa_flags = SA_SIGINFO;
    sigaction(SIGUSR1, &act, NULL);

    for (...) {
        pthread_mutex_lock(&iomutex);

        // do some calculations here...

        struct aiocb *cblist[];
        int cbno;
        // set up the aio request list - omitted

        struct sigevent sev;
        memset(&sev, 0, sizeof(struct sigevent));
        sev.sigev_notify = SIGEV_SIGNAL;
        sev.sigev_signo = SIGUSR1;
        sev.sigev_value.sival_ptr = &iomutex;

        lio_listio(LIO_NOWAIT, cblist, cbno, &sev);
    }

    // ensure that the last queued operation completes
    // before this function returns
    pthread_mutex_lock(&iomutex);
    pthread_mutex_unlock(&iomutex);
}

通知関数を使用したコード例 - 余分なスレッドが作成されるため、効率が低下する可能性があります。

void output() {
    pthread_mutex_t iomutex = PTHREAD_MUTEX_INITIALIZER;

    for (...) {
        pthread_mutex_lock(&iomutex);

        // do some calculations here...

        struct aiocb *cblist[];
        int cbno;
        // set up the aio request list - omitted

        struct sigevent sev;
        memset(&sev, 0, sizeof(struct sigevent));
        sev.sigev_notify = SIGEV_THREAD;
        sev_sigev_notify_function = &pthread_mutex_unlock;
        sev.sigev_value.sival_ptr = &iomutex;

        lio_listio(LIO_NOWAIT, cblist, cbno, &sev);
    }

    // ensure that the last queued operation completes
    // before this function returns
    pthread_mutex_lock(&iomutex);
    pthread_mutex_unlock(&iomutex);
}
4

1 に答える 1

3

lio_listio() 呼び出しで sigevent 引数を設定すると、その 1 つの特定の呼び出しですべてのジョブが完了すると、シグナル (または関数呼び出し) で通知されます。あなたはまだ必要があります:

  1. lio_listio() 呼び出しを行ったのと同じ数の通知を受け取るまで待って、それらがすべて完了したことを確認してください。

  2. おそらくグローバル変数を介して(移植可能にするために)、シグナルハンドラからメインスレッドに通信するための安全なメカニズムを使用してください。

Linux を使用している場合は、代わりに eventfd を sigevent に関連付けて、それを待つことをお勧めします。シグナル ハンドラを使用する必要がないため、より柔軟です。BSD (Mac OS ではない) では、kqueue を使用して aiocbs を待機でき、solaris/illumos では、ポートを使用して aiocb の完了の通知を受け取ることができます。

Linux で eventfds を使用する方法の例を次に示します。

ちなみに、lio_listio でジョブを発行するときは注意が必要です。2 つ以上のジョブの取得がサポートされているとは限りません。また、一部のシステムでは、一度に発行できるジョブ数の制限が非常に低くなっています。たとえば、Mac OS のデフォルトは 16 です。この制限は AIO_LISTIO_MAX マクロとして定義されている場合がありますが、必ずしもそうとは限りません。その場合、sysconf(_SC_AIO_LISTIO_MAX) を呼び出す必要があります ( docsを参照)。詳細については、lio_listio のドキュメントを参照してください。

少なくとも lio_listio() 呼び出しのエラー状態を確認する必要があります。

また、ミューテックスを使用するソリューションは、for ループ内の各ループを同期し、一度に 1 つずつ実行するため、最適ではありません (再帰ミューテックスでない限り、その場合、信号がハンドラーはたまたま別のスレッドに着陸します)。

より適切なプリミティブは、ハンドラーで解放されるセマフォである場合があり、(for ループの後) ループしたのと同じ回数を取得し、lio_listio() を呼び出します。ただし、Linux 固有でよいのであれば、eventfd をお勧めします。

于 2013-02-10T18:44:37.160 に答える