基本的な考え方
名前のないセマフォ (匿名セマフォとも呼ばれる) は、通常、プロセス間ではなくスレッド間の同期に使用される同期プリミティブです。名前付きセマフォ (プロセス間同期に使用される) とは対照的に、名前なしセマフォの有効期間は通常、それを作成したプロセスに制限されており、システム固有の名前は必要ありません。
以下は、POSIX の名前のないセマフォの詳細な紹介です。
- Value : セマフォには関連する整数値があり、最初は負でない整数に設定できます。
- P 操作/待機: スレッドが P 操作 (待機/ドロップ/取得操作とも呼ばれる) を実行しようとするとき、セマフォの値が 0 より大きい場合、値をデクリメントして続行します。値が 0 の場合、スレッドは値がゼロ以外になるまでブロックされます。
- V 操作/発行: V 操作 (または発行/増加/解放操作) を実行するスレッドは、セマフォの値を増加させます。P 操作によりブロックされたスレッドがある場合、そのうちの 1 つが起動されます。
メイン機能
-
初期化:
sem_init
int sem_init(sem_t *sem, int pshared, unsigned int value);
sem
: 初期化するセマフォへのポインタ。pshared
: ゼロ以外の場合、セマフォはプロセス間で共有されます。0 の場合、プロセスのスレッド間でのみ共有されます。value
: セマフォの初期値。
-
破壊する:
sem_destroy
int sem_destroy(sem_t *sem);
sem_init
初期化されたセマフォは破棄する前に使用してください。
-
待ってください:
sem_wait
int sem_wait(sem_t *sem);
- セマフォの値を減らします。値がすでに 0 である場合、セマフォの値が正になるまで呼び出しスレッドはブロックされます。
-
待ってみてください:
sem_trywait
int sem_trywait(sem_t *sem);
- セマフォの値を減らしてみてください。値が 0 の場合、関数はすぐに戻り、ブロックされません。
-
問題:
sem_post
int sem_post(sem_t *sem);
- セマフォの値を増やします。
sem_wait
その結果、いずれかのスレッドがブロックされた場合、そのうちの 1 つが起動されます。
- セマフォの値を増やします。
使用上の注意
-
名前のないセマフォは主にスレッド間の同期に使用されます。パラメータを使用するとプロセス間でパラメータを共有できますが
pshared
、この場合は通常、名前付きセマフォを使用する方が適切です。 -
セマフォが不要になった場合は、セマフォを破棄
sem_destroy
して関連リソースを解放する必要があります。 -
すべての同期プリミティブと同様、セマフォを使用するときはデッドロック、ライブロック、競合状態を避けるために注意する必要があります。
名前なしセマフォは、特にリソース アクセスの相互排他が必要な場合やスレッド間の連携が必要な場合に、スレッドにシンプルで効率的な同期メカニズムを提供するため、マルチスレッド アプリケーションで非常に役立ちます。
例
名前のないセマフォの簡単な例を見てみましょう。この例には、プロデューサー スレッドとコンシューマー スレッドの 2 つのスレッドがあります。プロデューサは整数を生成して共有変数に格納し、コンシューマはこの整数を読み取ります。2 つのスレッドは、コンシューマが整数を読み取る前にプロデューサが整数を生成し、プロデューサが新しい整数を生成する前にコンシューマが整数を読み取るように、名前のないセマフォを通じて同期されます。
#include <stdio.h>
#include <pthread.h>
#include <semaphore.h>
sem_t sem_prod, sem_cons;
int shared_var;
void *producer(void *arg) {
for (int i = 1; i <= 5; i++) {
sem_wait(&sem_prod); // Wait until the consumer has consumed the last item
shared_var = i; // Produce an item
printf("Produced: %d\n", shared_var);
sem_post(&sem_cons); // Signal the consumer that an item has been produced
}
return NULL;
}
void *consumer(void *arg) {
for (int i = 1; i <= 5; i++) {
sem_wait(&sem_cons); // Wait until the producer has produced an item
printf("Consumed: %d\n", shared_var);
sem_post(&sem_prod); // Signal the producer that the item has been consumed
}
return NULL;
}
int main() {
pthread_t prod_tid, cons_tid;
sem_init(&sem_prod, 0, 1); // Initialize the producer semaphore to 1
sem_init(&sem_cons, 0, 0); // Initialize the consumer semaphore to 0
pthread_create(&prod_tid, NULL, producer, NULL); // Create producer thread
pthread_create(&cons_tid, NULL, consumer, NULL); // Create consumer thread
pthread_join(prod_tid, NULL); // Wait for producer thread to finish
pthread_join(cons_tid, NULL); // Wait for consumer thread to finish
sem_destroy(&sem_prod); // Destroy the producer semaphore
sem_destroy(&sem_cons); // Destroy the consumer semaphore
return 0;
}
実行結果は以下の通りです。
majn@tiger:~/C_Project/prod_cons$ ./prod_cons
Produced: 1
Consumed: 1
Produced: 2
Consumed: 2
Produced: 3
Consumed: 3
Produced: 4
Consumed: 4
Produced: 5
Consumed: 5
この例では:
sem_prod
および は、sem_cons
プロデューサーとコンシューマー間の同期に使用される名前のないセマフォです。sem_prod
1 に初期化され、プロデューサー スレッドが最初に実行できるようになります。sem_cons
初期状態では消費可能なアイテムがないため、0に初期化されます。- プロデューサ スレッドは整数を生成し、それを に保存します
shared_var
。sem_post(&sem_cons)
その後、コンシューマはシグナルを介してこの整数を使用できます。 - コンシューマ スレッドは
sem_cons
セマフォを待機し、shared_var
セマフォから整数を読み取り、sem_post(&sem_prod)
生成のためにシグナルをプロデューサーに渡します。 - main 関数は、これら 2 つのスレッドが完了するのを待ち、最後にセマフォを破棄します。
[注意] 上記例では、pthread_t tid;
は新規作成したスレッドのスレッドIDを格納する変数です。pthread_create
この変数は宣言時に実際には初期化されていませんが、呼び出し時に明示的に設定されるため、これは問題ではありません。
を呼び出すとpthread_create
、その最初のパラメータはpthread_t
型の変数へのポインタになります。この変数は、新しく作成されたスレッドの ID を格納するために使用されます。pthread_create
スレッドが正常に作成された後に設定されます。
pthread_create と pthread_join
pthread_create
および は、pthread_join
POSIX スレッド (pthread と呼ばれることが多い) のプログラミングにおける 2 つの基本関数です。これらは、それぞれ新しいスレッドを作成し、スレッドが完了するのを待つために使用されます。以下に、これら 2 つの機能について詳しく説明します。
1.pthread_create
この関数は、新しいスレッドを作成するために使用されます。
試作品:
int pthread_create(pthread_t *thread, const pthread_attr_t *attr, void *(*start_routine) (void *), void *arg);
パラメータ:
thread
:pthread_t
新しいスレッドの ID を格納する型の変数へのポインタ。attr
:pthread_attr_t
へのポインタ。スレッドのプロパティを設定するために使用されます。に設定するとNULL
、デフォルトのプロパティが使用されます。start_routine
: 新しいスレッドが開始されたときに実行される関数。この関数はvoid *
1 つのパラメータを返し、受け入れる必要がありますvoid *
。arg
:start_routine
に渡されるパラメータ。
戻り値:
0
: 成功。- エラー番号: 失敗しました。
2.pthread_join
この関数は、スレッドが完了するのを待つために使用されます。呼び出し元のスレッドは、指定されたスレッドが完了するまでブロックされます。
試作品:
int pthread_join(pthread_t thread, void **retval);
パラメータ:
thread
: 待機するスレッドの ID。retval
:void *
へのポインタ。スレッドの戻り値をキャプチャするために使用されます。戻り値を気にしない場合は、 に設定できますNULL
。
戻り値:
0
: 成功。- エラー番号: 失敗しました。
例
pthread_create
と の使用方法の簡単な例を次に示しますpthread_join
。
#include <stdio.h>
#include <pthread.h>
void *print_hello(void *data) {
char *message = (char *)data;
printf("%s\n", message);
return NULL;
}
int main() {
pthread_t tid;
char *message = "Hello from the thread!";
// 创建一个新线程
if (pthread_create(&tid, NULL, print_hello, message) != 0) {
printf("Error creating thread.\n");
return 1;
}
// 等待线程完成
pthread_join(tid, NULL);
printf("Thread has finished execution.\n");
return 0;
}
上記の例では、main 関数は、関数を呼び出してprint_hello
メッセージをパラメータとして渡す新しいスレッドを作成します。main 関数は、pthread_join
スレッドの実行が完了するまで待機します。
majn@tiger:~/C_Project/pthread_project$ ./pthread_demo
Hello from the thread!
Thread has finished execution.