C 言語でセマフォを使用する

胡金庫 2023年10月12日
  1. POSIX セマフォを使用して C の共有変数へのアクセスを同期する
  2. sem_destroy 関数を使用して、名前のないセマフォを破棄する
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);
}
著者: 胡金庫
胡金庫 avatar 胡金庫 avatar

DelftStack.comの創設者です。Jinku はロボティクスと自動車産業で8年以上働いています。自動テスト、リモートサーバーからのデータ収集、耐久テストからのレポート作成が必要となったとき、彼はコーディングスキルを磨きました。彼は電気/電子工学のバックグラウンドを持っていますが、組み込みエレクトロニクス、組み込みプログラミング、フロントエンド/バックエンドプログラミングへの関心を広げています。

LinkedIn Facebook