Linux C++ マルチスレッド同期の非常に詳細な説明

Linux C++ マルチスレッド同期の非常に詳細な説明

背景の質問: 特定のアプリケーション シナリオでは、複数のスレッドを同期しないとどのような問題が発生しますか?

マルチウィンドウのチケット販売のマルチスレッドシミュレーションを例に挙げます。

#include <iostream>
#include<pthread.h>
#include <stdio.h>
#include<stdlib.h>
#include<文字列.h>
#include <unistd.h>

名前空間 std を使用します。

チケットの合計は20です。
void *sell_ticket(void *arg)
{
    (int i=0; i<20; i++) の場合
    {
        チケットの合計が0の場合
        {
            睡眠(1);
            cout<<"「<<20-ticket_sum+1<<"番目」を販売します<<endl;
            チケット合計--;
        }
    }
    0を返します。
}

int メイン()
{
    整数フラグ;
    pthread_t tids[4];

    (int i=0; i<4; i++) の場合
    {
        フラグ = pthread_create (&tids[i], NULL, &sell_ticket, NULL);
        if(フラグ)
        {
            cout<<"pthread 作成エラー、フラグ="<<フラグ<<endl;
            フラグを返します。
        }
    }

    睡眠(20);
    void *ans;
    (int i=0; i<4; i++) の場合
    {
        フラグ=pthread_join(tids[i],&ans);
        if(フラグ)
        {
            cout<<"tid="<<tids[i]<<"join erro flag="<<flag<<endl;
            フラグを返します。
        }
        cout<<"ans="<<ans<<endl;
    }
    0を返します。
}

分析: チケットは合計 20 枚しかありませんが、23 枚が販売されています。これは明らかに買われすぎと売られすぎの問題です。この問題の根本的な原因は、すべてのスレッドが同時に ticket_sum を読み書きできることです。

追伸:

1. 並行処理の場合、命令の実行順序はカーネルによって決定されます。同じスレッド内では命令が順番に実行されますが、異なるスレッド間ではどの命令が最初に実行されるかはわかりません。演算結果が異なるスレッドの実行順序に依存する場合、競合状態が形成されます。この場合、演算結果を予測することは難しいため、競合状態の形成は可能な限り避ける必要があります。

2. 競合状態を解決する最も一般的な方法は、以前に分離されていた 2 つの命令を分割できないアトミック操作に結合することです。アトミック操作には他のタスクを挿入できません。

3. マルチスレッドの場合、同期とは、一定期間内に 1 つのスレッドだけがリソースにアクセスでき、その期間中は他のスレッドがリソースにアクセスできないことを意味します。

4. スレッド同期の一般的な方法: ミューテックスロック、条件変数、読み取り/書き込みロック、セマフォ

1. ミューテックス

これは本質的に、ロックとロック解除の 2 つの状態を持つ特別なグローバル変数です。ロック解除されたミューテックスはスレッドによって取得できます。取得されると、ミューテックスはロックされ、ロック状態に変更されます。その後、スレッドのみがロックを開く権利を持ちます。他のスレッドがミューテックスを取得したい場合は、ミューテックスが再び開かれるまで待つ必要があります。

ミューテックス ロックを使用してリソースを同期します。

#include <iostream>
#include<pthread.h>
#include <stdio.h>
#include<stdlib.h>
#include<文字列.h>
#include <unistd.h>

名前空間 std を使用します。

チケットの合計は20です。
pthread_mutex_t mutex_x=PTHREAD_MUTEX_INITIALIZER; //静的初期化ミューテックス

void *sell_ticket(void *arg)
{
    (int i=0; i<20; i++) の場合
    {
        pthread_mutex_lock(&mutex_x);// ミューテックスロックによるアトミック操作
        チケットの合計が0の場合
        {
            睡眠(1);
            cout<<"「<<20-ticket_sum+1<<"番目」を販売します<<endl;
            チケット合計--;
        }
        pthread_mutex_unlock(&mutex_x);
    }
    0を返します。
}

int メイン()
{
    整数フラグ;
    pthread_t tids[4];

    (int i=0; i<4; i++) の場合
    {
        フラグ = pthread_create (&tids[i], NULL, &sell_ticket, NULL);
        if(フラグ)
        {
            cout<<"pthread 作成エラー、フラグ="<<フラグ<<endl;
            フラグを返します。
        }
    }

    睡眠(20);
    void *ans;
    (int i=0; i<4; i++) の場合
    {
        フラグ=pthread_join(tids[i],&ans);
        if(フラグ)
        {
            cout<<"tid="<<tids[i]<<"join erro flag="<<flag<<endl;
            フラグを返します。
        }
        cout<<"ans="<<ans<<endl;
    }
    0を返します。
}

分析: チケット販売のコア コード セグメントにミューテックス ロックを追加すると、アトミック操作になります。他のスレッドの影響を受けない

1. ミューテックスの初期化

ミューテックスの初期化は静的初期化と動的初期化に分かれています

静的: pthread_mutex_t mutex_x=PTHREAD_MUTEX_INITIALIZER; //静的初期化ミューテックス

動的: pthread_mutex_init 関数

ps: ミューテックスの静的初期化と動的初期化の違いは何ですか?

補足します。 。 。 。

2. ミューテックスロックの関連特性と分類

//ミューテックス属性を初期化します pthread_mutexattr_init(pthread_mutexattr_t attr);

// ミューテックス属性を破棄します pthread_mutexattr_destroy(pthread_mutexattr_t attr);

//ミューテックス ロック属性を取得するために使用されます int pthread_mutexattr_getpshared(const pthread_mutexattr_t *restrict attr, int *restrict pshared);

//ミューテックスロック属性を設定するために使用されます int pthread_mutexattr_setpshared(pthread_mutexattr_t *attr, int pshared);

attrはミューテックスの属性を表す

pshared は、ミューテックス ロックの共有属性を表し、次の 2 つの値が可能です。

1) PTHREAD_PROCESS_PRIVATE: ロックはプロセス内の2つのスレッド間の排他制御にのみ使用できます (デフォルト)

2) PTHREAD_PROCESS_SHARED: このロックは、2 つの異なるプロセス内のスレッドの相互排他制御に使用できます。このロックを使用する場合は、プロセス共有メモリにミューテックスを割り当て、ミューテックスの属性を指定する必要があります。

ミューテックスロックの分類:

//ミューテックスのタイプを取得します。 int pthread_mutexattr_gettype(const pthread_mutexattr_t *restrict attr, int *restrict type);

//ミューテックスのタイプを設定します int pthread_mutexattr_settype(const pthread_mutexattr_t *restrict attr, int type);

パラメータ type は、ミューテックス ロックのタイプを示します。ミューテックス ロックには次の 4 つのタイプがあります。

1.PTHREAD_MUTEX_NOMAL: 標準のミューテックスロック。最初のロックは成功し、2番目のロックは失敗してブロックされます。

2. PTHREAD_MUTEX_RECURSIVE: 再帰ミューテックスロック。最初のロックが成功すると、2 番目のロックも成功します。これは内部カウンタとして理解でき、各ロックカウンタは 1 を加算し、ロック解除は 1 を減算します。

3.PTHREAD_MUTEX_ERRORCHECK: ミューテックス ロックをチェックします。最初のロックは成功します。2 番目のロックはブロックせずにエラー メッセージを返します。

4.PTHREAD_MUTEX_DEFAULT: デフォルトのミューテックスロック。最初のロックは成功し、2番目のロックは失敗します。

3. ロック機能をテストする

int pthread_mutex_lock(&mutex): ロックがすでに占有されている場合、ロック関数がハングして待機するのではなく、EBUSY を返すことをテストします。もちろん、ロックが占有されていない場合は、ロックを取得できます。

2つのスレッドがリソースを奪い合う状況を明確にするために、1つの関数でテストロック関数を使用してロックし、もう1つの関数で通常のロック関数を使用してロックします。

#include <iostream>
#include<pthread.h>
#include <stdio.h>
#include<stdlib.h>
#include<文字列.h>
#include <unistd.h>
#include <errno.h>
名前空間 std を使用します。

チケットの合計は20です。
pthread_mutex_t mutex_x=PTHREAD_MUTEX_INITIALIZER; //静的初期化ミューテックス

void *sell_ticket_1(void *arg)
{
    (int i=0; i<20; i++) の場合
    {
        pthread_mutex_lock(&mutex_x);
        チケットの合計が0の場合
        {
            睡眠(1);
            cout<<"thread_1 は「<<20-ticket_sum+1<<」番目のチケットを販売します"<<endl;
            チケット合計--;
        }
        睡眠(1);
        pthread_mutex_unlock(&mutex_x);
        睡眠(1);
    }
    0を返します。
}

void *sell_ticket_2(void *arg)
{
    整数フラグ;
    (int i=0; i<10; i++) の場合
    {
        フラグ=pthread_mutex_trylock(&mutex_x);
        if(フラグ==EBUSY)
        {
            cout<<"sell_ticket_2:変数はsell_ticket_1によってロックされています"<<endl;
        }
        そうでない場合(フラグ==0)
        {
            チケットの合計が0の場合
            {
                睡眠(1);
                cout<<"thread_2 は「<<20-ticket_sum+1<<」番目のチケット」を販売します<<endl;
                チケット合計--;
            }
            pthread_mutex_unlock(&mutex_x);
        }
        睡眠(1);
    }
    0を返します。
}
int メイン()
{
    整数フラグ;
    pthread_t tids[2];

    フラグ = pthread_create (&tids[0], NULL, &sell_ticket_1, NULL);
    if(フラグ)
    {
        cout<<"pthread 作成エラー、フラグ="<<フラグ<<endl;
        フラグを返します。
    }

    フラグ = pthread_create (&tids[1], NULL, &sell_ticket_2, NULL);
    if(フラグ)
    {
        cout<<"pthread 作成エラー、フラグ="<<フラグ<<endl;

        フラグを返します。
    }

    void *ans;
    睡眠(30);
    フラグ=pthread_join(tids[0],&ans);
    if(フラグ)
    {
        cout<<"tid="<<tids[0]<<"join erro flag="<<flag<<endl;
        フラグを返します。
    }
    それ以外
    {
        cout<<"ans="<<ans<<endl;
    }

    フラグ=pthread_join(tids[1],&ans);
    if(フラグ)
    {
        cout<<"tid="<<tids[1]<<"join erro flag="<<flag<<endl;
        フラグを返します。
    }
    それ以外
    {
        cout<<"ans="<<ans<<endl;
    }

    0を返します。
}

分析: ロック機能をテストすることで、2 つのスレッドがリソースを競合する状況を明確に確認できます。

2. 条件変数

ミューテックスは万能ではありません。たとえば、スレッドが共有データで条件が発生するのを待機している場合、データ オブジェクトを繰り返しロックおよびロック解除 (ポーリング) する必要があることがあります。ただし、このようなポーリングは非常に時間がかかり、リソースを大量に消費し、非常に非効率的であるため、ミューテックス ロックはこの状況には適していません。

特定の条件が満たされるのを待っている間、スレッドをスリープ状態にし、条件が満たされると、特定の条件が満たされるのを待っている間スリープ状態のスレッドを切り替えるメソッドが必要です。

このような方法を実装できれば、プログラムの効率は間違いなく大幅に向上します。この方法こそが条件変数です。

例:

#include <iostream>
#include<pthread.h>
#include <stdio.h>
#include<stdlib.h>
#include<文字列.h>
#include <unistd.h>
#include <errno.h>
名前空間 std を使用します。

pthread_cond_t qready=PTHREAD_COND_INITIALIZER; //条件
pthread_mutex_t qlock=PTHREAD_MUTEX_INITIALIZER; //ミューテックス

整数x=10、y=20;

void *f1(void *arg)
{
  cout<<"f1 開始"<<endl;
  pthread_mutex_lock(&qlock);
  x<y の場合
  {
    pthread_cond_wait(&qready,&qlock);
  }
  pthread_mutex_unlock(&qlock);
  睡眠(3);
  cout<<"f1 終了"<<endl;
  0を返します。
}

void *f2(void *arg)
{
  cout<<"f2 開始"<<endl;
  pthread_mutex_lock(&qlock);
  20 より小さい
  y=10;
  cout<<"が変更されました、x="<<x<<" y="<<y<<endl;
  pthread_mutex_unlock(&qlock);
  もし(x>y)
  {
    pthread_cond_signal(&qready);
  }
  cout<<"f2 終了"<<endl;
  0を返します。
}

int メイン()
{
  pthread_t tids[2];
  整数フラグ;

  フラグ = pthread_create (&tids[0], NULL, f1, NULL);
  if(フラグ)
  {
    cout<<"pthread 1 作成エラー "<<endl;
    フラグを返します。
  }

  睡眠(2);

  フラグ = pthread_create (&tids[1], NULL, f2, NULL);
  if(フラグ)
  {
    cout<<"pthread 2 作成エラー "<<endl;
    フラグを返します。
  }

  睡眠(5);
  0を返します。
}

分析: スレッド 1 は条件が満たされていないためブロックされ、次にスレッド 2 が実行されて条件が変更されます。スレッド 2 はスレッド 1 に条件変更通知を発行し、次にスレッド 2 が終了し、次にスレッド 1 が実行を継続し、最後にスレッド 1 が終了します。スレッド 1 が最初に実行されるように、スレッド 2 を作成する前に 2 秒間スリープします。

追伸:

1. 条件変数は、実行中のスレッドをブロックし、別のスレッドがシグナルを送信するのを待つことで、ミューテックス ロックの欠点を補います。条件変数は、ミューテックス ロックと一緒に使用されることがよくあります。条件変数を使用すると、スレッドをブロックできます。条件が満たされない場合、スレッドは対応するミューテックス ロックのロックを解除し、条件が変化するのを待つことがよくあります。別のスレッドが条件変数を変更すると、対応する条件変数にラインを切り替えるように通知します。この条件変数によってブロックされている 1 つ以上のスレッドは、ミューテックス ロックを再度ロックし、条件が満たされているかどうかを再テストします。

1. 条件変数の関連機能

1) 作成する

静的メソッド: pthread_cond_t cond PTHREAD_COND_INITIALIZER

動的メソッド: int pthread_cond_init(&cond,NULL)

Linuxスレッドによって実装された条件変数は属性をサポートしていないため、NULL(cond_attrパラメータ)

2) ログアウト

int pthread_cond_destory(&cond)

条件変数は、条件変数にスレッドが存在しない場合にのみ登録解除できます。それ以外の場合は EBUSY が返されます。

Linux によって実装された条件変数はリソースを割り当てないため、ログアウト アクションには待機中のスレッドがあるかどうかのチェックのみが含まれます。(条件変数の基礎となる実装を参照してください)

3) 待つ

条件付き待機: int pthread_cond_wait(&cond,&mutex)

時間指定待機: int pthread_cond_timewait(&cond,&mutex,time)

1. 指定された時間内に条件が満たされない場合は、ETIMEOUT が返され、待機が終了します。

2. 待機方法に関係なく、複数のスレッドが同時に pthread_cond_wait を要求して競合状態が発生するのを防ぐために、ミューテックス ロックが必要です。

3. スレッドはpthread_cond_waitを呼び出す前にロックする必要がある

4) 刺激

待機中のスレッドを刺激する: pthread_cond_signal(&cond)

すべての待機スレッドを刺激する: pthread_cond_broadcast(&cond)

重要なことは、pthread_cond_signal には、雷鳴のような群れ効果がないということです。つまり、最大で 1 つの待機中のスレッドに信号が送信され、すべてのスレッドに信号が送信されてスレッドが起動し、各スレッドがリソースを競い合うように要求されることはありません。

pthread_cond_signal は、待機中のスレッドの優先度と待機時間に基づいて、どの待機中のスレッドをトリガーするかを決定します。

プログラムを見て問題点を見つけてみましょう。

#include <iostream>
#include<pthread.h>
#include <stdio.h>
#include<stdlib.h>
#include<文字列.h>
#include <unistd.h>
#include <errno.h>
名前空間 std を使用します。

pthread_cond_t taxe_cond=PTHREAD_COND_INITIALIZER; //タクシー到着条件
pthread_mutex_t tax_mutex=PTHREAD_MUTEX_INITIALIZER; // 同期ミューテックス

void *旅行者_到着(void *名前)
{
    cout<<"旅行者:"<<(char*)name<<"は今すぐタクシーが必要です!"<<endl;
    pthread_mutex_lock(&taxi_mutex);
    pthread_cond_wait(&taxi_cond,&taxi_mutex);
    pthread_mutex_unlock(&taxi_mutex);
    cout<<"旅行者:"<<(char*)name<<" はタクシーに乗りました!"<<endl;
    pthread_exit((void*)0);
}

void *タクシー到着(void *名前)
{
    cout<<"タクシー:"<<(char*)name<<"到着者."<<endl;
    pthread_cond_signal(&taxi_cond);
    pthread_exit((void*)0);
}

int メイン()
{
    pthread_t tids[3];
    整数フラグ;

    フラグ = pthread_create (&tids[0], NULL,taxi_arrive, (void*)("Jack"));
    if(フラグ)
    {
        cout<<"pthread_create エラー:flag="<<flag<<endl;
        フラグを返します。
    }
    cout<<「時間が経つ」<<endl;
    睡眠(1);

    フラグ = pthread_create(&tids[1],NULL,traveler_arrive,(void*)("スーザン"));
    if(フラグ)
    {
        cout<<"pthread_create エラー:flag="<<flag<<endl;
        フラグを返します。
    }
    cout<<「時間が経つ」<<endl;
    睡眠(1);

    フラグ = pthread_create (&tids[2], NULL,taxi_arrive, (void*)("Mike"));
    if(フラグ)
    {
        cout<<"pthread_create エラー:flag="<<flag<<endl;
        フラグを返します。
    }
    cout<<「時間が経つ」<<endl;
    睡眠(1);

    void *ans;
    (int i=0; i<3; i++) の場合
    {
        フラグ=pthread_join(tids[i],&ans);
        if(フラグ)
        {
            cout<<"pthread_join エラー:flag="<<flag<<endl;
            フラグを返します。
        }
        cout<<"ans="<<ans<<endl;
    }
    0を返します。
}

分析: このプログラムは、タクシーが到着したことを乗客に知らせる条件変数と同期ロックで構成されています。乗客は到着後、車を待ちます (条件変数)。タクシーが到着すると、乗客に通知されます。乗客のスーザンが到着した後、彼女は最初に到着したジャックの車に乗らず、マイクの車が到着するまで待ってからマイクの車に乗ったことがわかります。ジャックの車はアイドル状態でした。なぜこのようなことが起こったのでしょうか?コードを分析してみましょう。ジャックのタクシーが到着した後、pthread_cond_signal(&taxi_cond) が呼び出され、乗客がいないことがわかり、スレッドが直接終了していることがわかります。 。 。 。

正しい操作は次のようになります。最初に到着したジャックは、乗客がいないことに気づき、乗客を待ちました。乗客が到着した場合、彼はすぐに出発し、乗客の数を数えます。

次の改善を行います。

1. 乗客カウンターを追加して、他の乗客を待つのではなく、乗客が到着したらすぐにタクシーが出発できるようにします(スレッドが停止中)

2. タクシー到着関数に while ループを追加します。乗客がいない場合は、乗客が到着するまで待機します。

#include <iostream>
#include<pthread.h>
#include <stdio.h>
#include<stdlib.h>
#include<文字列.h>
#include <unistd.h>
#include <errno.h>
名前空間 std を使用します。

pthread_cond_t taxe_cond=PTHREAD_COND_INITIALIZER; //タクシー到着条件
pthread_mutex_t tax_mutex=PTHREAD_MUTEX_INITIALIZER; // 同期ミューテックス


void *旅行者_到着(void *名前)
{
    cout<<"旅行者:"<<(char*)name<<"は今すぐタクシーが必要です!"<<endl;
    pthread_mutex_lock(&taxi_mutex);
 
    pthread_cond_wait(&taxi_cond,&taxi_mutex);
    pthread_mutex_unlock(&taxi_mutex);
    cout<<"旅行者:"<<(char*)name<<" はタクシーに乗りました!"<<endl;
    pthread_exit((void*)0);
}

void *タクシー到着(void *名前)
{
    cout<<"タクシー:"<<(char*)name<<"到着者."<<endl;
    
    pthread_exit((void*)0);
}

int メイン()
{
    pthread_t tids[3];
    整数フラグ;

    フラグ = pthread_create (&tids[0], NULL,taxi_arrive, (void*)("Jack"));
    if(フラグ)
    {
        cout<<"pthread_create エラー:flag="<<flag<<endl;
        フラグを返します。
    }
    cout<<「時間が経つ」<<endl;
    睡眠(1);

    フラグ = pthread_create(&tids[1],NULL,traveler_arrive,(void*)("スーザン"));
    if(フラグ)
    {
        cout<<"pthread_create エラー:flag="<<flag<<endl;
        フラグを返します。
    }
    cout<<「時間が経つ」<<endl;
    睡眠(1);

    フラグ = pthread_create (&tids[2], NULL,taxi_arrive, (void*)("Mike"));
    if(フラグ)
    {
        cout<<"pthread_create エラー:flag="<<flag<<endl;
        フラグを返します。
    }
    cout<<「時間が経つ」<<endl;
    睡眠(1);

    void *ans;
    (int i=0; i<3; i++) の場合
    {
        フラグ=pthread_join(tids[i],&ans);
        if(フラグ)
        {
            cout<<"pthread_join エラー:flag="<<flag<<endl;
            フラグを返します。
        }
        cout<<"ans="<<ans<<endl;
    }
    0を返します。
}

3. 読み取り書き込みロック

複数のスレッドが同時に読み取ることはできますが、複数のスレッドが同時に書き込むことはできません。

1. 読み取り/書き込みロックはミューテックスロックよりも適用性が高く、並列性が高い

2. 読み取り/書き込みロックは、データ構造に対する読み取り操作の数が書き込み操作の数を超える状況に最適です。

3. ロックが読み取りモードの場合、スレッド間で共有できますが、ロックが書き込みモードの場合、排他的になることしかできないため、読み取り/書き込みロックは共有排他ロックとも呼ばれます。

4. 読み取り/書き込みロックには、強力な読み取り同期と強力な書き込み同期の2つの戦略があります。

強力な読み取り同期では、リーダーには常に高い優先順位が与えられます。ライターが書き込み操作を実行しない限り、リーダーはアクセス権を取得できます。

強力な書き込み同期では、書き込み側には常に高い優先順位が与えられ、読み取り側は待機中または実行中の書き込みがすべて完了した後にのみ読み取りが可能になります。

システムによって使用する戦略は異なります。たとえば、フライト予約システムでは強力な書き込み同期を使用し、ライブラリ参照システムでは強力な読み取り同期を使用します。

さまざまなビジネスシナリオに応じてさまざまな戦略を採用する

1) 読み取り/書き込みロックの初期化解除

静的初期化: pthread_rwlock_t rwlock=PTHREAD_RWLOCK_INITIALIZER

動的初期化: int pthread_rwlock_init(rwlock, NULL)、NULLは読み取り/書き込みロックがデフォルトの属性を使用することを意味します

読み取り/書き込みロックを破棄します: int pthread_rwlock_destory(rwlock)

読み取り/書き込みロックのリソースを解放する前に、pthread_rwlock_destory 関数を使用して読み取り/書き込みロックをクリーンアップする必要があります。 pthread_rwlock_init関数によって割り当てられたリソースを解放します。

読み取り/書き込みロックでデフォルト以外の属性を使用する場合、attrはNULLにできず、attrに値を割り当てる必要があります。

int pthread_rwlockattr_init(attr)、attrを初期化する

int pthread_rwlockattr_destory(attr)、attr を破棄する

2) 書き込みモードでロックを取得し、読み取りモードでロックを取得し、読み取り書き込みロックを解除する

int pthread_rwlock_rdlock(rwlock)、読み取りモードでロックを取得する

int pthread_rwlock_wrlock(rwlock)、書き込みモードでロックを取得する

int pthread_rwlock_unlock(rwlock)、ロックを解除する

上記の 2 つのロック取得方法はどちらもブロッキング機能です。つまり、ロックを取得できない場合、呼び出しスレッドはすぐには戻らず、実行をブロックします。書き込み操作が必要な場合、このブロッキングロック取得方法は非常に不適切です。考えてみてください。書き込みが必要なのに、ロックを取得できないだけでなく、ここで待機する必要があり、効率が大幅に低下します。

したがって、非ブロッキング方式でロックを取得する必要があります。

int pthread_rwlock_tryrdlock(rwlock)

整数 pthread_rwlock_trywrlock(rwlock)

読み取り/書き込みロックの例:

#include <iostream>
#include<pthread.h>
#include <stdio.h>
#include<stdlib.h>
#include<文字列.h>
#include <unistd.h>
#include <errno.h>
名前空間 std を使用します。

整数値=5;
pthread_rwlock_t rwlock;

void *リーダー(void *引数)
{
  pthread_rwlock_rdlock(&rwlock);
  cout<<"リーダー "<<(long)arg<<" がロックを取得しました"<<endl;
  pthread_rwlock_unlock(&rwlock);
  0を返します。
}

void *ライター(void *引数)
{
  pthread_rwlock_wrlock(&rwlock);
  cout<<"writer "<<(long)arg<<" がロックを取得しました"<<endl;
  pthread_rwlock_unlock(&rwlock);
  0を返します。
}

int メイン()
{
  整数フラグ;
  長いn=1、m=1;
  pthread_t wid、rid;
  pthread_attr_t 属性;

  フラグ=pthread_rwlock_init(&rwlock,NULL);
  if(フラグ)
  {
    cout<<"rwlock 初期化エラー"<<endl;
    フラグを返します。
  }

  pthread_attr_init(&attr);
  pthread_attr_setdetachstate(&attr,PTHREAD_CREATE_DETACHED);//スレッド分離

  (int i=0;i<num;i++) の場合
  {
    もし(i%3)
    {
      pthread_create(&rid,&attr,リーダー,(void *)n);
      cout<<"リーダーを作成"<<n<<endl;
      n++;
    }それ以外
    {
      pthread_create(&wid,&attr,writer,(void *)m);
      cout<<"ライターを作成"<<m<<endl;
      m++;
    }
  }

  sleep(5);//他の完了を待つ
  0を返します。
}

分析: 読み取りスレッド 3 つ、書き込みスレッド 2 つ、読み取りスレッドが書き込みスレッドより多い

読み取り/書き込みロックが書き込み状態の場合、ロックが解除される前に、ロックをロックしようとするすべてのスレッドがブロックされます。

読み取り/書き込みロックが読み取り状態の場合、ロックがロック解除される前に、読み取りモードでロックしようとするすべてのスレッドはアクセスできますが、書き込みモードでロックしようとするスレッドはブロックされます。

したがって、読み取り/書き込みロックはデフォルトで強力な読み取りモードになります。

4. セマフォ

セマフォ(SEM)とミューテックスロックの違い:ミューテックスロックでは1つのスレッドのみがクリティカルセクションに入ることができますが、セマフォでは複数のスレッドがクリティカルセクションに入ることができます。

1) セマフォの初期化

int sem_init(&sem,pshared,v)

pshared は 0 であり、このセマフォが現在のプロセスのローカル セマフォであることを示します。

pshared は 1 です。これは、このセマフォを複数のプロセス間で共有できることを意味します。

vはセマフォの初期値です

成功した場合は0を返し、失敗した場合は-1を返します。

2) 信号値の加算と減算

int sem_wait(&sem): アトミック操作でセマフォの値を1減らします

int sem_post(&sem): アトミック操作でセマフォ値に1を加算する

3) セマフォをクリーンアップする

int sem_destory(&sem)

セマフォを使用して、2つのウィンドウと10人のゲストにサービスを提供するプロセスをシミュレートします。

例:

#include <iostream>
#include<pthread.h>
#include <stdio.h>
#include<stdlib.h>
#include<文字列.h>
#include <unistd.h>
#include <errno.h>
#include <セマフォ.h>
名前空間 std を使用します。


整数=10;
sem_t sem;

void *get_service(void *cid)
{
  int id=*((int*)cid);
  sem_wait(&sem) == 0 の場合
  {
     睡眠(5);
     cout<<"顧客 "<<id<<" サービスを取得する"<<endl;
     cout<<"顧客 "<<id<<" 完了 "<<endl;
     sem_post(&sem);
  }
  0を返します。
}

int メイン()
{
  sem_init(&sem,0,2);
  pthread_t 顧客[番号];
  整数フラグ;

  (int i=0;i<num;i++) の場合
  {
    整数 id = i;
    フラグ=pthread_create(&customer[i],NULL,get_service,&id);
    if(フラグ)
    {
      cout<<"pthread 作成エラー"<<endl;
      フラグを返します。
    }それ以外
    {
      cout<<"顧客 "<<i<<" が到着しました "<<endl;
    }
    睡眠(1);
  }

  //すべてのスレッドが完了するまで待つ
  (int j=0;j<num;j++) の場合
  {
    pthread_join(顧客[j],NULL);
  }
  sem_destroy(&sem);
  0を返します。
}

分析: セマフォの値は、アイドル状態のサービス ウィンドウを表します。各ウィンドウは、一度に 1 人のユーザーのみにサービスを提供できます。アイドル状態のウィンドウがある場合、サービスが開始される前はセマフォは -1 で、サービスが完了するとセマフォは +1 になります。

概要: Linux C++ スレッド同期の 4 つの方法:ミューテックス ロック、条件変数、読み取り/書き込みロック、セマフォ

Linux C++ マルチスレッド同期メソッドの超詳細な説明に関するこの記事はこれで終わりです。Linux C++ マルチスレッド同期に関するより関連性の高いコンテンツについては、123WORDPRESS.COM の以前の記事を検索するか、以下の関連記事を引き続き参照してください。今後とも 123WORDPRESS.COM をよろしくお願いいたします。

以下もご興味があるかもしれません:
  • C++ でマルチスレッドとスレッド同期を実装する方法
  • C++ マルチスレッドで条件変数が使用されるのはなぜですか?
  • 複数のクライアントと同時に通信する TCP サーバーを実装するための C++ マルチスレッド
  • C++11 新機能 マルチスレッド操作の実践
  • C++ マルチスレッド戻り値メソッドの詳細な説明
  • 電子辞書を実装するための C++ マルチスレッド
  • C++11 並行プログラミング: マルチスレッド std::thread
  • C++ マルチスレッドでのロックと条件変数の使用に関するチュートリアル
  • C++ マルチスレッドにおけるデッドロック発生の分析 (2 つの要約と 6 つの例を含む)
  • C++ マルチスレッド強制終了の詳細

<<:  入力のid属性とname属性の違いの例

>>:  グリーンスタイルのウェブデザイン作品18点の最新コレクション

推薦する

Nginx に lua-nginx-module モジュールをインストールする方法

ngx_lua_module は、lua パーサーを nginx に埋め込み、lua 言語で記述され...

Tomcat 例外の解決方法 (リクエスト ターゲットに無効な文字が見つかりました。有効な文字は RFC 7230 および RFC 3986 で定義されています)

1. シナリオ表示Tomcat ログに次の例外情報が時々報告されます。何が起こっているのでしょうか...

Docker で ElasticSearch と Kibana をインストールするためのサンプル コード

1. はじめにElasticsearchは現在非常に人気があり、多くの企業が利用しているため、esを...

フロントエンド開発における一般的なテクニックのまとめ

1. 記事タイトルリストの右側に日付を表​​示する方法:コードをコピーコードは次のとおりです。 &l...

ウェブフロントエンドコードを書く際の考慮事項のまとめ

1. HTMLタグの前に次のような文を追加するのが最適です。 <!DOCTYPE HTML P...

JavaScriptのスリープ関数の使用

目次1.スリープ機能2.タイムアウトを設定する3. 約束4. 非同期待機5. 1秒後に出力1、2秒後...

vue+antv でレーダーチャートを実装するためのサンプルコード

1. 依存関係をダウンロードするnpm インストール @antv/データセットnpm インストール ...

MySQLに画像を保存する方法

1 はじめにデータベースを設計する場合、画像や音声ファイルをデータベースに挿入することは避けられませ...

Linux プロセスの CPU 使用率が 700% に達し、終了できない場合の解決策

目次1. 問題の発見2. プロセスの詳細情報を表示する3. 解決策4. 大法を再開する1. 問題の発...

JS の原価と基準価額の問題に関する簡単な分析

プリミティブ値 -> プリミティブ型Number String Boolean undefin...

HTML を使用して IE8 および IE9 の互換表示モードを無効にするヒント

IE 8 以降では互換モードが追加され、これを有効にすると IE の下位バージョンでレンダリングされ...

中国語ウェブコンテンツを紹介する10の経験

<br /> テキスト、シンボル、リンクの 3 つの側面に焦点を当て、主に中国語で、私の...

Reactでコンポーネントを作成する方法

目次序文コンポーネントの紹介クラスコンポーネントの作成状態についてレンダリングについて関数コンポーネ...

Vue プロジェクトがページング効果を実現

ページング効果は、参考までにvueプロジェクトに実装されています。具体的な内容は次のとおりです。 1...