C 言語でセマフォを使用する
この記事では、C 言語でセマフォを使用する方法に関する複数の方法を示します。
POSIX セマフォを使用して C の共有変数へのアクセスを同期する
UNIX ベースのシステムには 2つの一般的なセマフォ API があります。POSIX セマフォと SystemV セマフォです。後者は、POSIX API と同じ機能を提供しながら、インターフェースがそれほど単純ではないと考えられています。セマフォはミューテックスのようなさらに別の同期メカニズムであり、ほとんど同様のシナリオで利用できることに注意してください。セマフォはカーネルによって維持される整数であり、通常は 0
以上の初期値に設定されます。
セマフォオブジェクトに対して 2つの操作を実行できます。1つインクリメントまたはデクリメントします。これは、共有リソースの取得と解放に対応します。POSIX は、名前のないセマフォ用の特別な sem_t
タイプを提供します。これは、マルチスレッドワークフローでより一般的なツールです。sem_t
変数は、sem_init
関数で初期化する必要があります。この関数は、指定されたセマフォをプロセス間またはプロセスのスレッド間で共有する必要があるかどうかも示します。変数が初期化されると、関数 sem_post
および sem_wait
を使用して同期を実装できます。sem_post
はセマフォをインクリメントします。これは通常、共有リソースのロック解除に対応します。対照的に、sem_wait
はセマフォをデクリメントし、リソースのロックを示します。したがって、クリティカルセクションは sem_wait
で始まり、sem_post
呼び出しで終わる必要があります。ただし、成功ステータスコードのチェックは、コードのデバッグに不可欠な場合があることに注意してください。
#include <pthread.h>
#include <semaphore.h>
#include <stdio.h>
#include <stdlib.h>
static long shared = 0;
static sem_t sem;
enum { THREADS = 4 };
#define errExit(msg) \
do { \
perror(msg); \
exit(EXIT_FAILURE); \
} while (0)
static void *threadFunc(void *arg) {
long loops = *((long *)arg);
for (long j = 0; j < loops; j++) {
if (sem_wait(&sem) == -1) errExit("sem_wait");
shared++;
if (sem_post(&sem) == -1) errExit("sem_post");
}
return NULL;
}
int main(int argc, char *argv[]) {
pthread_t t[THREADS];
int s;
long nloops;
if (argc != 2) {
fprintf(stderr, "Usage: %s num_loops\n", argv[0]);
exit(EXIT_FAILURE);
}
nloops = strtol(argv[1], NULL, 0);
if (sem_init(&sem, 0, 1) == -1) errExit("sem_init");
for (int i = 0; i < THREADS; ++i) {
s = pthread_create(&t[i], NULL, threadFunc, &nloops);
if (s != 0) errExit("pthread_create");
}
for (int i = 0; i < THREADS; ++i) {
s = pthread_join(t[i], NULL);
if (s != 0) errExit("pthread_join");
}
printf("shared = %ld\n", shared);
exit(EXIT_SUCCESS);
}
サンプルコマンド:
./program_name 1000
出力:
shared = 4000
sem_destroy
関数を使用して、名前のないセマフォを破棄する
sem_init
呼び出しで初期化されたセマフォは、sem_destroy
関数を使用して破棄する必要があります。ただし、sem_destroy
は、待機しているプロセス/スレッドがないときに呼び出す必要があることに注意してください。sem_destroy
呼び出しを省略すると、一部のシステムでメモリリークが発生する可能性があります。
一般に、セマフォは Pthread ミューテックスと比較して同様のパフォーマンスを示しますが、コード構造を改善するために、通常は後者が推奨されます。ただし、シグナルハンドラーからロックを変更する必要があるシナリオがいくつかあります。これには、関数が非同期セーフである必要があり、sem_post
のみがそのように実装されます。POSIX API には名前付きセマフォもあり、それを作成して使用したスレッドが終了した後も存続する可能性があります。
#include <pthread.h>
#include <semaphore.h>
#include <stdio.h>
#include <stdlib.h>
static long shared = 0;
static sem_t sem;
enum { THREADS = 4 };
#define errExit(msg) \
do { \
perror(msg); \
exit(EXIT_FAILURE); \
} while (0)
static void *threadFunc(void *arg) {
long loops = *((long *)arg);
for (long j = 0; j < loops; j++) {
if (sem_wait(&sem) == -1) errExit("sem_wait");
shared++;
if (sem_post(&sem) == -1) errExit("sem_post");
}
return NULL;
}
int main(int argc, char *argv[]) {
pthread_t t[THREADS];
int s;
long nloops;
if (argc != 2) {
fprintf(stderr, "Usage: %s num_loops\n", argv[0]);
exit(EXIT_FAILURE);
}
nloops = strtol(argv[1], NULL, 0);
if (sem_init(&sem, 0, 1) == -1) errExit("sem_init");
for (int i = 0; i < THREADS; ++i) {
s = pthread_create(&t[i], NULL, threadFunc, &nloops);
if (s != 0) errExit("pthread_create");
}
for (int i = 0; i < THREADS; ++i) {
s = pthread_join(t[i], NULL);
if (s != 0) errExit("pthread_join");
}
printf("shared = %ld\n", shared);
sem_destroy(&sem);
exit(EXIT_SUCCESS);
}