マルチプロセス プログラミング - POSIX の名前のないセマフォ

基本的な考え方

名前のないセマフォ (匿名セマフォとも呼ばれる) は、通常、プロセス間ではなくスレッド間の同期に使用される同期プリミティブです。名前付きセマフォ (プロセス間同期に使用される) とは対照的に、名前なしセマフォの有効期間は通常、それを作成したプロセスに制限されており、システム固有の名前は必要ありません。

以下は、POSIX の名前のないセマフォの詳細な紹介です。

  1. Value : セマフォには関連する整数値があり、最初は負でない整数に設定できます。
  2. P 操作/待機: スレッドが P 操作 (待機/ドロップ/取得操作とも呼ばれる) を実行しようとするとき、セマフォの値が 0 より大きい場合、値をデクリメントして続行します。値が 0 の場合、スレッドは値がゼロ以外になるまでブロックされます。
  3. V 操作/発行: V 操作 (または発行/増加/解放操作) を実行するスレッドは、セマフォの値を増加させます。P 操作によりブロックされたスレッドがある場合、そのうちの 1 つが起動されます。

メイン機能

  1. 初期化:sem_init

    int sem_init(sem_t *sem, int pshared, unsigned int value);
    
    • sem: 初期化するセマフォへのポインタ。
    • pshared: ゼロ以外の場合、セマフォはプロセス間で共有されます。0 の場合、プロセスのスレッド間でのみ共有されます。
    • value: セマフォの初期値。
  2. 破壊する:sem_destroy

    int sem_destroy(sem_t *sem);
    
    • sem_init初期化されたセマフォは破棄する前に使用してください。
  3. 待ってください:sem_wait

    int sem_wait(sem_t *sem);
    
    • セマフォの値を減らします。値がすでに 0 である場合、セマフォの値が正になるまで呼び出しスレッドはブロックされます。
  4. 待ってみてください:sem_trywait

    int sem_trywait(sem_t *sem);
    
    • セマフォの値を減らしてみてください。値が 0 の場合、関数はすぐに戻り、ブロックされません。
  5. 問題: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_prod1 に初期化され、プロデューサー スレッドが最初に実行できるようになります。
  • sem_cons初期状態では消費可能なアイテムがないため、0に初期化されます。
  • プロデューサ スレッドは整数を生成し、それを に保存しますshared_varsem_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_joinPOSIX スレッド (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.

Guess you like

Origin blog.csdn.net/weixin_43844521/article/details/133240795
Recommended