スレッドメインループ
前回は poll のメインループを使ったので、今回はスレッドメインループを使って、イベントを待ってみます。
メインループの関数の詳細はこちら。
イベントループのスレッドを開始、または停止させます。
pa_threaded_mainloop_stop() の実行前に、ロックは解除しておくこと。
スレッドを開始させた後は、内部のスレッドで、自動的にサーバーからのイベントが受け取られて、処理されます。
クライアント側は、コールバック関数をセットして、各イベントを処理することになりますが、実行されたコールバック関数は、イベントループのスレッド内で実行される形になるので、注意してください。
この時、ロック状態は維持されます。
イベントループのロックを行うと、スレッド内のイベントループの処理が一時停止します。
メインスレッドで PulseAudio の操作を行う場合は、イベント処理を行わないようにする必要があります。
でないと、不測のタイミングで、何らかのコールバック関数が来る可能性があります。
ロックは再帰的に行われるので、lock と unlock は、対で実行する必要があります。
スレッドメインループを使う場合、他のスレッドと同期を行う必要があります。
例えば、イベントループのスレッド内で、特定のコールバック関数が実行されて、特定の状況になったら、もう一方のスレッドで処理を進めるという形です。
この場合は、pa_threaded_mainloop_wait() と pa_threaded_mainloop_signal() を使います。
pa_threaded_mainloop_wait() を実行すると、イベントループのスレッドから実行されたコールバック関数内で、pa_threaded_mainloop_signal() が実行されるまで、待ちます。
コールバック関数内で、条件を満たすような状況になった場合は、pa_threaded_mainloop_signal() を実行すると、pa_threaded_mainloop_wait() で待機しているすべてのスレッドにシグナルを送り、wait を終了させます。
これにより、イベントループのスレッド側でトリガーが発生するまで、処理を待つという動作が行えます。
なお、wait を実行する前に、pa_threaded_mainloop_lock() で、ロック状態にしておく必要があります。
イベントを待っている間はロックが解除され、関数が戻る直前に、再度ロックされます。
※コールバック関数は、ロック状態で実行されます。
signal で、wait_for_accept 引数が 0 以外の場合は、pa_threaded_mainloop_accept() と一緒に使います。
メインループの関数の詳細はこちら。
作成など
メインループの作成などは、以下の関数を使います。
pa_threaded_mainloop *pa_threaded_mainloop_new(void); void pa_threaded_mainloop_free(pa_threaded_mainloop *m); pa_mainloop_api *pa_threaded_mainloop_get_api(pa_threaded_mainloop *m);
イベントループの開始/停止
int pa_threaded_mainloop_start(pa_threaded_mainloop *m); void pa_threaded_mainloop_stop(pa_threaded_mainloop *m);
イベントループのスレッドを開始、または停止させます。
pa_threaded_mainloop_stop() の実行前に、ロックは解除しておくこと。
スレッドを開始させた後は、内部のスレッドで、自動的にサーバーからのイベントが受け取られて、処理されます。
クライアント側は、コールバック関数をセットして、各イベントを処理することになりますが、実行されたコールバック関数は、イベントループのスレッド内で実行される形になるので、注意してください。
この時、ロック状態は維持されます。
ロック
void pa_threaded_mainloop_lock(pa_threaded_mainloop *m); void pa_threaded_mainloop_unlock(pa_threaded_mainloop *m);
イベントループのロックを行うと、スレッド内のイベントループの処理が一時停止します。
メインスレッドで PulseAudio の操作を行う場合は、イベント処理を行わないようにする必要があります。
でないと、不測のタイミングで、何らかのコールバック関数が来る可能性があります。
ロックは再帰的に行われるので、lock と unlock は、対で実行する必要があります。
待つ
void pa_threaded_mainloop_wait(pa_threaded_mainloop *m); void pa_threaded_mainloop_signal(pa_threaded_mainloop *m, int wait_for_accept); void pa_threaded_mainloop_accept(pa_threaded_mainloop *m);
スレッドメインループを使う場合、他のスレッドと同期を行う必要があります。
例えば、イベントループのスレッド内で、特定のコールバック関数が実行されて、特定の状況になったら、もう一方のスレッドで処理を進めるという形です。
この場合は、pa_threaded_mainloop_wait() と pa_threaded_mainloop_signal() を使います。
pa_threaded_mainloop_wait() を実行すると、イベントループのスレッドから実行されたコールバック関数内で、pa_threaded_mainloop_signal() が実行されるまで、待ちます。
コールバック関数内で、条件を満たすような状況になった場合は、pa_threaded_mainloop_signal() を実行すると、pa_threaded_mainloop_wait() で待機しているすべてのスレッドにシグナルを送り、wait を終了させます。
これにより、イベントループのスレッド側でトリガーが発生するまで、処理を待つという動作が行えます。
なお、wait を実行する前に、pa_threaded_mainloop_lock() で、ロック状態にしておく必要があります。
イベントを待っている間はロックが解除され、関数が戻る直前に、再度ロックされます。
※コールバック関数は、ロック状態で実行されます。
signal で、wait_for_accept 引数が 0 以外の場合は、pa_threaded_mainloop_accept() と一緒に使います。
pa_threaded_mainloop_accept
pa_threaded_mainloop_accept() は、実行されたコールバック関数に渡された、引数などのデータを、wait から戻ったスレッド側で使用したい時に使います。
まず、コールバック関数内で、pa_threaded_mainloop_signal() の wait_for_accept 引数を、0 以外にして実行します。
wait から戻った後、コールバック関数内のデータを参照し、その後、pa_threaded_mainloop_accept() を実行します。
これにより、accept が実行されるまでは、コールバック内のデータが有効な状態になります。
まず、コールバック関数内で、pa_threaded_mainloop_signal() の wait_for_accept 引数を、0 以外にして実行します。
wait から戻った後、コールバック関数内のデータを参照し、その後、pa_threaded_mainloop_accept() を実行します。
これにより、accept が実行されるまでは、コールバック内のデータが有効な状態になります。
プログラム
スレッドメインループを使って、サーバーと接続するまで待つプログラムです。
$ cc -o 03-thread 03-thread.c -lpulse
#include <stdio.h> #include <stdlib.h> #include <pulse/pulseaudio.h> pa_threaded_mainloop *ml; pa_context *ctx; static void _appfree(void) { pa_context_unref(ctx); pa_threaded_mainloop_free(ml); } static void _enderr(void) { int err = pa_context_errno(ctx); printf("[error] (%d) %s\n", err, pa_strerror(err)); _appfree(); exit(1); } //コールバック static void _callback(pa_context *c,void *userdata) { pa_context_state_t state; state = pa_context_get_state(c); switch(state) { case PA_CONTEXT_CONNECTING: printf(">CONNECTING\n"); break; case PA_CONTEXT_AUTHORIZING: printf(">AUTHORIZING\n"); break; case PA_CONTEXT_SETTING_NAME: printf(">SETTING_NAME\n"); break; case PA_CONTEXT_READY: printf(">READY\n"); break; case PA_CONTEXT_FAILED: printf(">FAILED\n"); break; case PA_CONTEXT_TERMINATED: printf(">TERMINATED\n"); break; default: printf(">state: %d\n", state); break; } //wait を終了させる if(state == PA_CONTEXT_READY || state == PA_CONTEXT_FAILED) pa_threaded_mainloop_signal(ml, 0); } int main(void) { pa_context_state_t state; ml = pa_threaded_mainloop_new(); ctx = pa_context_new(pa_threaded_mainloop_get_api(ml), "test-pulse"); //接続 printf("connect..\n"); pa_context_set_state_callback(ctx, _callback, NULL); if(pa_context_connect(ctx, NULL, 0, NULL) < 0) _enderr(); //待つ pa_threaded_mainloop_lock(ml); pa_threaded_mainloop_start(ml); while(1) { state = pa_context_get_state(ctx); if(state == PA_CONTEXT_READY || state == PA_CONTEXT_FAILED) break; pa_threaded_mainloop_wait(ml); } pa_threaded_mainloop_unlock(ml); pa_threaded_mainloop_stop(ml); //切断 if(state == PA_CONTEXT_READY) pa_context_disconnect(ctx); //解放 _appfree(); return 0; }
解説
やっている事自体は、poll のメインループとあまり変わりませんが、PulseAudio のメインループ処理が、別のスレッド内で自動処理されている点で異なります。
pa_threaded_mainloop_start() を実行した時点で、スレッドが作成され、イベント処理が自動的に始まりますが、その前にロックをしているので、実際にはまだ、イベントループは始まっていません。
実際にイベント処理が実行されるのは、pa_threaded_mainloop_wait() が実行されている間だけです。
connect 後、接続状態が確定していない場合は、pa_threaded_mainloop_wait() を実行して、状態が確定するまで待ちます。
待っている間はロックが解除され、イベントループスレッド内で、イベント処理が行われます。
サーバーからのイベントが処理され、コンテキスト状態変化のコールバック関数が来た時に、READY か FAILED の状態になった場合は、pa_threaded_mainloop_signal() を実行して、シグナルを送ります。
これにより、メインスレッドの wait が終了して、戻ってきます。
今回の場合は、READY または FAILED の状態にならない限り、pa_threaded_mainloop_wait() 関数は戻ってきません。
(ただし、エラー等の理由で戻ってくる可能性もあるので、戻ってきた後に状態のチェックを行うことは必要です)
pa_threaded_mainloop_start() を実行した時点で、スレッドが作成され、イベント処理が自動的に始まりますが、その前にロックをしているので、実際にはまだ、イベントループは始まっていません。
実際にイベント処理が実行されるのは、pa_threaded_mainloop_wait() が実行されている間だけです。
connect 後、接続状態が確定していない場合は、pa_threaded_mainloop_wait() を実行して、状態が確定するまで待ちます。
待っている間はロックが解除され、イベントループスレッド内で、イベント処理が行われます。
サーバーからのイベントが処理され、コンテキスト状態変化のコールバック関数が来た時に、READY か FAILED の状態になった場合は、pa_threaded_mainloop_signal() を実行して、シグナルを送ります。
これにより、メインスレッドの wait が終了して、戻ってきます。
今回の場合は、READY または FAILED の状態にならない限り、pa_threaded_mainloop_wait() 関数は戻ってきません。
(ただし、エラー等の理由で戻ってくる可能性もあるので、戻ってきた後に状態のチェックを行うことは必要です)
メインループの違い
poll のメインループは、主に、プログラム内で PulseAudio の処理だけを行う場合に使います。
ただし、プログラム内で独自にスレッドを作成して、その中でメインループを実行させれば、並行処理させることはできます。
こちらの利点は、メインループを自分で処理できるということです。
スレッドのメインループは、PulseAudio のメインループを別スレッドで実行しつつ、メインスレッドで他の処理を行うことができます。
実行環境に左右されずに、同じコードでスレッドの処理を行えるのが利点ですが、メインループは自分で処理できません。
どちらを使うかは、プログラマーの自由です。
なお、シンプル API の場合は、スレッドメインループを使って実装されています。
ただし、プログラム内で独自にスレッドを作成して、その中でメインループを実行させれば、並行処理させることはできます。
こちらの利点は、メインループを自分で処理できるということです。
スレッドのメインループは、PulseAudio のメインループを別スレッドで実行しつつ、メインスレッドで他の処理を行うことができます。
実行環境に左右されずに、同じコードでスレッドの処理を行えるのが利点ですが、メインループは自分で処理できません。
どちらを使うかは、プログラマーの自由です。
なお、シンプル API の場合は、スレッドメインループを使って実装されています。