こんにちは!
今回の記事ではC言語でのスレッド間でのイベントの送受信について例を見ながら解説します。
スレッドはC言語でも苦手としている人も多いと思います。
この記事で少しでも力になれた嬉しいです。サンプルコードもありますが是非自分でも書いてみてください!
では本題に入りましょう!
基本的な例
まずは基本的なコードを見ていきましょう。最初にサンプルコードを載せます。
#include <stdio.h>
#include <pthread.h>
pthread_mutex_t mutex; // ミューテックス
pthread_cond_t condition; // 条件変数
int event = 0; // イベント
void* sender(void* arg) {
// イベントを生成して通知する
pthread_mutex_lock(&mutex);
event = 1;
pthread_cond_signal(&condition);
pthread_mutex_unlock(&mutex);
return NULL;
}
void* receiver(void* arg) {
// イベントの受信を待機する
pthread_mutex_lock(&mutex);
while (event == 0) {
pthread_cond_wait(&condition, &mutex);
}
// イベントを受信した後の処理
printf("Event received!\n");
pthread_mutex_unlock(&mutex);
return NULL;
}
int main() {
pthread_t sender_thread, receiver_thread;
pthread_mutex_init(&mutex, NULL);
pthread_cond_init(&condition, NULL);
// スレッドの作成
pthread_create(&sender_thread, NULL, sender, NULL);
pthread_create(&receiver_thread, NULL, receiver, NULL);
// スレッドの終了待ち
pthread_join(sender_thread, NULL);
pthread_join(receiver_thread, NULL);
pthread_mutex_destroy(&mutex);
pthread_cond_destroy(&condition);
return 0;
}
このコードを見ながら解説していきます。必ず自分でもコードを書いて実行して見てください。
まず全体的にこのコードが何をしているかです。
この例では、スレッド間でイベントの送受信を行っています。sender
関数はイベントを生成し、receiver
関数はイベントの受信を待機します。
条件変数を使用して、イベントの通知と待機を行っています。
もう少し具体的に見ていきましょう。
①sender関数が実行されミューテックスをロック
②変数eventに値1を設定
③pthread_cond_signal
関数を使用して、待機中のスレッドにイベントの通知を送信
④ミューテックスをアンロック
⑤receiver
関数が実行され、ミューテックスをロック
⑥イベントが発生するまで、pthread_cond_wait
関数で条件変数の待機
⑦イベントが通知されると、待機を終了し、処理を続行します。ここでは単純に「Event received!」と表示
⑧ミューテックスをアンロック
⑨pthread_join
関数によって、メインスレッドが子スレッドの終了を待機します。
なんとなく理解できたでしょうか。ミューテックスや条件変数などについても詳しく見ていきましょう。
ミューテックスとは
ミューテックス(Mutex)は、複数のスレッドが共有データへのアクセスを制御するために使用される同期プリミティブ(synchronization primitive)です。
ミューテックスは「相互排他(mutual exclusion)」の略称であり、その目的は、同時に複数のスレッドが共有データを変更することを防ぎ、データの整合性や競合条件(race condition)の発生を防ぐことです。
ミューテックスを利用することで、共有データを安全に使うことができます。
ミューテックスについてもう少し踏み込んでみます。
ミューテックスは以下の操作を提供します。
ロック(Lock)またはロックの取得
ミューテックスをロックすることで、他のスレッドからのアクセスを排除します。
ロックが取得されている間は、他のスレッドはそのミューテックスをロックできず、排他的なアクセスが保証されます。
ロックしている間は共有データを専有するということです。
アンロック(Unlock)またはロックの解放
ミューテックスのロックを解除し、他のスレッドがミューテックスをロックできるようにします。これにより、他のスレッドが共有データにアクセスできるようになります。
つまり共有データの専有の解除です。
条件変数とは
条件変数は、スレッド間の待機と通知のメカニズムを提供するための同期プリミティブです。条件変数は、複数のスレッドが特定の条件が満たされるのを待機し、他のスレッドがその条件を満たしたことを通知するために使用されます。
条件変数は先ほど紹介したミューテックスと一緒に利用されます。そうすることで共有データを安全に使いながらスレッドの待機と通知が実現できます。
条件変数で使用する関数をみていきましょう。
①pthread_cond_init()
: 条件変数を初期化します。
②pthread_cond_wait()
: 条件変数の待機状態に入ります。スレッドはこの関数を呼び出すと待機し、他のスレッドから通知を受けるまでブロックされます。通常、この関数はミューテックスと組み合わせて使用され、ミューテックスがロックされた状態で呼び出されます。pthread_cond_wait()
内ではミューテックスが一時的にアンロックされ、他のスレッドがミューテックスを使用できるようになります。
③pthread_cond_signal()
: 条件変数の待機中のスレッドのうち、一つのスレッドを通知します。通知されたスレッドは待機から抜け出し、処理を再開します。
④pthread_cond_broadcast()
: 条件変数の待機中の全てのスレッドを通知します。待機中の全てのスレッドが同時に処理を再開します。
以上が条件変数の説明です。
では先ほどのサンプルコードの中のpthread_cond_signal()について見ていきましょう。
pthread_cond_signal()は先ほど説明したとおり待機中のスレッドの内、一つだけが通知を受け取ります。
今回のプログラムでは待機中のスレッドが一つしかないので通知を受け取るスレッドは決まりますが、複数のスレッドが待機している場合は、どのスレッドが通知を受け取るかはそのスレッドの実装に依存します。
次にmain()の中身を見ていきましょう。
①pthread_t
型の変数 sender_thread
と receiver_thread
を宣言。
これらの変数は各スレッドの識別子を保持するために使用されます。
②pthread_mutex_init()
関数を使用して、mutex
という名前のミューテックスを初期化します。これにより、ミューテックスが使用可能な状態になります。
③pthread_cond_init()
関数を使用して、condition
という名前の条件変数を初期化します。これにより、条件変数が使用可能な状態になります。
④pthread_create()
関数を使用して、sender
関数とreceiver
関数を実行する2つのスレッドを作成します。これにより、新しいスレッドが作成され、それぞれの関数がそれぞれのスレッドで実行されるようになります。
⑤pthread_join()
関数を使用して、メインスレッドが各スレッドの終了を待機します。これにより、メインスレッドは各スレッドが処理を完了するまで待機し、スレッドの実行が終了すると次のステップに進みます。
⑥pthread_mutex_destroy()
関数を使用して、初期化したミューテックスを解放します。
これにより、ミューテックスが破棄され、関連するリソースが解放されます。
⑦pthread_cond_destroy()
関数を使用して、初期化した条件変数を解放します。これにより、条件変数が破棄され、関連するリソースが解放されます。
以上がmain()の中身です。
ここで4に関して、スレッドを作成しただけで関数が実行されるの?と思った方もいるでしょう。私も最初に学んだ頃にそう感じた記憶があります。。
答えは”Yes”です。
最初はそこを理解するのが難しいと思いますが、そういうものだと思ってください。
以上で基本的なプログラムの解説は終了です!
長くなってしまったので応用編は別の記事で作成しようと思います!そちらも参考にして見てくださいね!
コメント