スレッドロック:ミューテックスロック、スピンロック、読み取り/書き込みロック、条件変数、セマフォ

ミューテックス(ロック):

1.ミューテックスを定義する

pthread_mutex_t mutex;

2. mutexを初期化します

静的态分配
pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZED

动态分配
int pthread_mutex_init(pthread_mutex_t * restrict mutex、 
          const pthread_mutexattr_t * restrict attr);

最初にコードを見てください

#include <stdio.h>
#include <stdlib.h>
#include <pthread.h>         //创建两个线程,分别对两个全变量进行++操作,判断两个变量是否相等,不相等打印

int a = 0;
int b = 0;
// 未初始化 和0初始化的成员放在bbs
pthread_mutex_t mutex;

void* route()
{
    while(1)            //初衷不会打印
    {
        a++;
        b++;
        if(a != b)
        {
            printf("a =%d, b = %d\n", a, b);
            a = 0;
            b = 0;
        }
    }
}

int main()
{
    pthread_t tid1, tid2;
    pthread_create(&tid1, NULL, route, NULL);
    pthread_create(&tid2, NULL, route, NULL);
    
    pthread_join(tid1, NULL);
    pthread_join(tid2, NULL);
    return 0;
}

セグメントコードの演算結果の利点は、予想を超えています。

予想していた構造は印刷されるべきではなく、ここでは予期しない結果が印刷されました。同等のデータが印刷されても、なぜこれが起こっているのですか?

説明: 2つのスレッドが互いのCPUリソースを横取りします。1つのスレッドがグローバル変数に対して++操作を実行した後、出力操作を比較する時間がありませんでした。他のスレッドがCPUを横取りして印刷出力を比較します。この状況を回避するには、以下で説明するミューテックスロックを使用する必要があります。

 

ミューテックス(ロック):重要なコードセグメントを保護して排他的アクセスを保証するために使用されます。

1. mutexを定義します:pthread_mutex_t mutex;
2. mutexを初期化します:pthread_mutex_init(&mutex、NULL); // 2番目のパラメーターは調査されず、NULLに設定されます; //初期化は1(メモリのみ)
3.ロックpthread_mutex_lock (&mutex); 1-> 0; 0 wait // mutexが利用できない場合はハングし、CPUをあきらめる
4. pthread_mutex_unlock(&mutex);ロックを解除して1に戻す

5.销毁pthread_mutex_destroy(&mutex);  

戻り値:成功した場合は0を、エラーが発生した場合はエラー番号を返します。

注: ミューテックスは、複数のスレッドが共有リソースにアクセスする場合、共有リソースにアクセスする前にミューテックスをロックし、アクセス後にロックを解除します。ミューテックスがロックされると、他のスレッドがブロックされます。現在のスレッドにアクセスしてロックが解除されるまで。mutexの解放時に複数のスレッドがブロックされると、ブロックされたすべてのスレッドが実行可能になり、実行可能になった最初のスレッドがmutexをロックできます。これにより、一度に1つのスレッドのみが共有リソースにアクセスすることが保証されます。

この時点で、相互排他ロックによって上記の問題を解決できるようです。

#include <stdio.h>
#include <stdlib.h>
#include <pthread.h>

int a = 0;
int b = 0;
// 未初始化 和0初始化的成员放在bbs
pthread_mutex_t mutex;

void* route()
{
    while(1)            //初衷不会打印
    {
        pthread_mutex_lock(&mutex);   
        a++;
        b++;
        if(a != b)
        {
            printf("a =%d, b = %d\n", a, b);
            a = 0;
            b = 0;
        }
        pthread_mutex_unlock(&mutex);
    }
}

int main()
{
    pthread_t tid1, tid2;
    pthread_mutex_init(&mutex, NULL);
    pthread_create(&tid1, NULL, route, NULL);
    pthread_create(&tid2, NULL, route, NULL);
    
    pthread_join(tid1, NULL);
    pthread_join(tid2, NULL);
    pthread_mutex_destroy(&mutex);

    return 0;
}

次のシナリオが存在します。スレッド1とスレッド2、スレッド1が関数Aを実行し、スレッド2が関数Bを実行します。これで、A関数とB関数の実行プロセスのロックとロック解除にそれぞれ1つのロックのみが使用されます。

#include <stdio.h>
#include <stdlib.h>
#include <pthread.h>
#include <unistd.h>           //线程的取消动作发生在加锁和解锁过程中时,当发生线程2取消后而没有进行解锁时,就会出现线程1将一直阻塞

pthread_mutex_t mutex;

void* odd(void* arg)
{
  int i = 1;
  for(; ; i+=2)
  {
    pthread_mutex_lock(&mutex);
    printf("%d\n", i);
    pthread_mutex_unlock(&mutex);
  }
}

void* even(void* arg)
{
  int i = 0;
  for(; ; i+=2)
  {
    pthread_mutex_lock(&mutex);
    printf("%d\n", i);
    pthread_mutex_unlock(&mutex);
  }
}


int main()
{
    pthread_t t1, t2;
    pthread_mutex_init(&mutex, NULL);
    pthread_create(&t1, NULL, even, NULL);
    pthread_create(&t2, NULL, odd, NULL);
    //pthread_create(&t3, NULL, even, NULL);
    
    sleep(3);
    pthread_cancel(t2);             //取消线程2,这个动作可能发生在线程2加锁之后和解锁之前

    pthread_join(t1, NULL);
    pthread_join(t2, NULL);
    pthread_mutex_destroy(&mutex);

    return 0;
}

制限ケースは次のとおりです。スレッド2のロック解除の前にスレッド2のキャンセルが発生すると、ロックが解除されていないため、スレッド1は実行を継続できなくなります

このような問題を解決するには、次のマクロ関数を使用できます。

マクロ://スレッドコールバック関数を登録します。これは、キャンセル後にスレッドがロック解除されないという問題を防ぐために使用できます

void pthread_cleanup_push(void(* routine)(void *)、//コールバック関数
                                              void * arg); //
コールバック関数のパラメーター//コールバック関数の実行タイミング
           1.pthread_exit
           2.pthread_cancel

           3. cleanaup_popパラメータが0ではない。cleanup_popが実行されると、コールバック関数が呼び出される

void pthread_cleanup_pop(int execute);

#include <stdio.h>
#include <stdlib.h>
#include <pthread.h>
#include <unistd.h>           //线程的取消动作发生在加锁和解锁过程中时,当发生线程2取消后而没有进行解锁时,就会出现线程1将一直阻塞

pthread_mutex_t mutex;

void callback(void* arg)      //在cancel中进行解锁
{
  printf("callback\n");
  sleep(1);
  pthread_mutex_unlock(&mutex); 
}

void* odd(void* arg)
{
  int i = 1;
  for(; ; i+=2)
  {
    pthread_cleanup_push(callback, NULL);//因为调用了cancel函数,从而触发了回调函数。
    pthread_mutex_lock(&mutex);
    printf("%d\n", i);
    pthread_mutex_unlock(&mutex);
    pthread_cleanup_pop(0);
  }
}

void* even(void* arg)
{
  int i = 0;
  for(; ; i+=2)
  {
    pthread_mutex_lock(&mutex);
    printf("%d\n", i);
    pthread_mutex_unlock(&mutex);
  }
}


int main()
{
    pthread_t t1, t2;
    pthread_mutex_init(&mutex, NULL);
    pthread_create(&t1, NULL, even, NULL);
    pthread_create(&t2, NULL, odd, NULL);
    //pthread_create(&t3, NULL, even, NULL);
    
    sleep(3);
    pthread_cancel(t2);             //取消线程2,这个动作可能发生在线程2加锁之后和解锁之前
    //pthread_mutex_unlock(&mutex);   有问题,如果执行even的程序有两个,而一个取消线程的函数执行时正好t3函数阻塞,就会导致t3和t1同时在执行even

    pthread_join(t1, NULL);
    pthread_join(t2, NULL);
    pthread_mutex_destroy(&mutex);

    return 0;
}

注:

1.ロックされたミューテックスを破棄しないでください。破棄されたミューテックスにより、後で使用されるスレッドがなくなります。

2.ロック機能とロック解除機能はペアで使用する必要があります

3.適切なロックの粒度(数値)を選択します。粒度が粗すぎる場合、多くのスレッドがブロックして同じロックを待機するため、並行性の改善は最小限になります。ロックの粒度が細かすぎると、ロックのオーバーヘッドが多すぎてシステムのパフォーマンスに影響し、コードが非常に複雑になります。

4.システムの負荷を軽減するために、最小(範囲)ロックを追加する必要があります

ミューテックスロックの使用は、デッドロックを回避するために注意を払う必要があります。「Linux High-Performance Server Programming」14.5.3は、リクエストのデッドロック問題の順序のために2つのミューテックスを導入

          スレッドが同じミューテックスを2回ロックしようとすると、スレッド自体がデッドロック状態になります。ミューテックスを使用するときにデッドロックを生成する他のそれほど明白でない方法があります。たとえば、プログラムで複数のミューテックスが使用されている場合、スレッドが最初のミューテックスを常に占有することが許可され、2番目のミューテックスをロックしようとするとブロックされますが、2番目のミューテックスがありますスレッドも最初のミューテックスをロックしようとしており、デッドロックが発生します。両方のスレッドが他のスレッドが所有するリソースを要求しているため、どちらのスレッドも順方向に実行できず、デッドロックが発生します。

          mutexがロックされる順序を注意深く制御することにより、デッドロックを回避できます。たとえば、ミューテックスAとBの両方を同時にロックする必要があるとします。すべてのスレッドがミューテックスBをロックする前に常にミューテックスAをロックする場合、これら2つのミューテックスを使用しても死に至ることはありません。ロック(もちろん、他のリソースでもデッドロックが発生する可能性があります);同様に、すべてのスレッドが常にミューテックスAをロックする前にミューテックスBをロックする場合、デッドロックは発生しません。デッドロックが発生するのは、あるスレッドが別のスレッドとは逆の順序でミューテックスをロックしようとした場合のみです。

         デッドロックを処理するために、実際のプログラミングで同期ミューテックスを追加することに加えて、次の3つの原則を使用してデッドロックコードの記述を回避することもできます。

1>短い:できるだけ簡潔にコードを書く

2> Ping:コードに複雑な関数呼び出しはありません

3>高速:コードの実行速度は可能な限り高速です

 

スピンロック:リアルタイム要件が高い場合に使用されます(欠点:CPUの浪費)

pthread_mutex_spin;

pthread_spin_lock(); //使用不可、busyloopを待機してビ​​ジー状態、常にCPUを使用中;ミューテックスロックはハングし、使用不可になると待機してCPUを放棄する

pthread_spin_unlock(); 

 pthread_spin_destroy(pthread_spinlock_t * lock);

pthread_spin_init(pthread_spinlock_t * lock、int pshared);

 

読み取り/書き込みロック(共有排他ロック):アプリケーションシナリオ---読み取り操作が多く、書き込み操作が少ない

注:読み取りと読み取りの共有、読み取りと書き込みの相互排他、高い書き込み優先度(同時に)

1. pthread_rwlock_t rwlock; //定義

2.int pthread_rwlock_init()//初期化

3. pthread_rwlock_rdlock()// pthread_rwlock_wrlock //读锁/写锁

4.pthread_rwlock_unlock()//ロック解除

5.int pthread_rwlock_destroy(pthread_rwlock_t * rwlock); //销毁锁

戻り値:正常終了0、エラー戻りエラー番号

注:ジョブをキューに追加したり、ジョブをキューから削除したりする場合は、書き込みロックを使用してください。

キューを検索するときは常に、最初に読み取りモードでロックを取得し、すべてのワーカースレッドがキューを同時に検索できるようにします。この場合、スレッドのみ

キューを検索する頻度は、ジョブを追加または削除するときよりもはるかに高く、読み取り/書き込みロックを使用するとパフォーマンスが向上する場合があります。

#include <stdio.h>
#include <stdlib.h>
#include <pthread.h>
#include <unistd.h>                //创建8个线程,3个写线程,5个读线程

pthread_rwlock_t rwlock;
int counter = 0;

void* readfunc(void* arg)
{
  int id = *(int*)arg;
  free(arg);
  while(1)
  {
    pthread_rwlock_rdlock(&rwlock);
    printf("read thread %d : %d\n", id, counter);
    pthread_rwlock_unlock(&rwlock);
    usleep(100000);
  }
}

void* writefunc(void* arg)
{
  int id = *(int*)arg;
  free(arg);
  while(1)
  {
    int t = counter;
    pthread_rwlock_wrlock(&rwlock);
    printf("write thread %d : t= %d,  %d\n", id, t, ++counter);
    pthread_rwlock_unlock(&rwlock);
    usleep(100000);
  }
}
int main()
{
    pthread_t tid[8];
    pthread_rwlock_init(&rwlock, NULL);
    int i = 0;
    for(i = 0; i < 3; i++)
    {
      int* p =(int*) malloc(sizeof(int));
      *p = i;
      pthread_create(&tid[i], NULL, writefunc, (void*)p);
    }
    for(i = 0; i < 5; i++)
    {
      int* p = (int*)malloc(sizeof(int));
      *p = i;
      pthread_create(&tid[3+i], NULL, readfunc, (void*)p);
    }

    for(i = 0; i < 8; i++)
    {
      pthread_join(tid[i], NULL);
    }

    pthread_rwlock_destroy(&rwlock);

    return 0;
}

 

条件変数: 

mutexを使用してスレッドのアクセスを共有データに同期する場合、条件変数は、スレッド間で共有データを同期するために使用される値です。条件変数は、スレッド間の通信メカニズムを提供します。共有データが特定の値に達すると、共有データを待機しているスレッドを起動します       

1. condが条件変数pthread_cond_tを定義します。
2.初期化てpthread_cond_init(指揮&、NULL);
3.待ち状態pthread_cond_waitの(&指揮、&ミューテックス);
                                 ミューテックス:ミューテックスが存在しない、環境にない場合は
                                 、相互に排他的な環境の中で:待機ミューテックス機能が設定されます1.リターンを待ち、ミューテックスは元の値に戻ります
4.条件pthread_cond_signal(&cond);を変更します
5. 条件pthread_cond_destroy(&cond);を破棄します。

规范写法:
pthread_mutex_lock();
    while(条件不满足)
    pthread_cond_wait();
//为什么会使用while?
//因为pthread_cond_wait是阻塞函数,可能被信号打断而返回(唤醒),返回后从当前位置向下执行, 被信号打断而返回(唤醒),即为假唤醒,继续阻塞
pthread_mutex_unlock();

pthread_mutex_lock();
pthread_cond_signal(); //信号通知   ----   如果没有线程在等待,信号会被丢弃(不会保存起来)。
pthread_mutex_unlock();
#include <stdio.h>
#include <pthread.h>
#include <unistd.h>
#include <stdlib.h>             //创建两个线程一个wait print,一个signal sleep()

pthread_cond_t cond;
pthread_mutex_t mutex;

void* f1(void* arg)
{
  while(1)
  {
    pthread_cond_wait(&cond, &mutex);
    printf("running!\n");
  }
}
void* f2(void* arg)
{
  while(1)
  {
    sleep(1);
    pthread_cond_signal(&cond);
  }
}

int main()
{
  pthread_t tid1, tid2;
  pthread_cond_init(&cond, NULL);
  pthread_mutex_init(&mutex, NULL);

  pthread_create(&tid1, NULL, f1, NULL);
  pthread_create(&tid2, NULL, f2, NULL);

  pthread_join(tid1, NULL);
  pthread_join(tid2, NULL);

  pthread_cond_destroy(&cond);
  pthread_mutex_destroy(&mutex);
  return 0;
}

System V //カーネルの永続性に基づく

セマフォ:POSIX //ファイルの永続性に基づくセマフォ


1.セマフォを定義します:sem_t sem;
2.セマフォを初期化します:sem_init(sem_t * sem、
                                                int shared、// 0は、プロセス内のスレッド数
                                                int val); //
セマフォの初期値3.PV操作int sem_wait(sem_t * sem); // sem-; 0未満の場合、Pオペレーションをブロックする
                      int sem_post(sem_t * sem); // sem ++; Vオペレーション
4. sem_destroy(sem_t * sem);を破棄する

 

拡張学習:

楽観的ロックと悲観的ロック?

楽観的ロック:

     リレーショナルデータベース管理システムでは、オプティミスティック同時実行制御(「オプティミスティックロック」とも呼ばれ、オプティミスティック同時実行制御、略して「OCC」)は、同時実行制御の方法です。複数のユーザーの同時トランザクションは処理時に互いに影響を及ぼさず、各トランザクションはロックを生成せずに影響するデータの一部を処理できると想定しています。データ更新を送信する前に、各トランザクションは、トランザクションがデータを読み取った後に、トランザクションがデータを変更したかどうかを確認します。他のトランザクションが更新されると、コミットされているトランザクションはロールバックされます。

オプティミスティック同時実行制御トランザクションには、次の段階が含まれ 
ます。1.読み取り:トランザクションはデータをキャッシュに読み取り、システムはトランザクションにタイムスタンプを割り当てます。 
2.確認:トランザクションが完了したら、それを送信します。この時点で、すべてのトランザクションが同期的に検証されます。トランザクションによって読み取られたデータが読み取り後に他のトランザクションによって変更されると、競合が発生し、トランザクションが中断されてロールバックされます。 

3.書き込み:検証フェーズを通過した後、更新されたデータをデータベースに書き込みます。

長所と短所:

       オプティミスティック並行性制御では、トランザクション間のデータ競合の可能性は比較的小さいと考えられているため、可能な限り直接実行する必要があり、コミットされるまでロックされないため、ロックやデッドロックは発生しません。ただし、これを直接行うだけでは、予期しない結果が発生する可能性があります。たとえば、2つのトランザクションがデータベースの行を読み取り、変更後にデータベースに書き戻す場合、問題が発生します。

悲観的ロック:

    リレーショナルデータベース管理システムでは、ペシミスティック同時実行制御(「ペシミスティックロック」、ペシミスティック同時実行制御、略して「PCC」とも呼ばれます)は、同時実行制御の方法です。トランザクションが他のユーザーに影響を与える方法でデータを変更するのを防ぐことができます。トランザクションによって実行される操作がデータの行を読み取り、ロックが適用される場合、トランザクションがロックを解放した場合にのみ、他のトランザクションがロックと競合する操作を実行できます。

利点と欠点:悲観的な同時実行制御は、実際には「アクセス前のフェッチロック」という保守的な戦略であり、データ処理のセキュリティを保証します。ただし、効率の面では、ロックを処理するメカニズムにより、データベースで追加のオーバーヘッドが発生し、デッドロックの可能性が高くなります。また、読み取り専用トランザクションで競合が発生しないため、ロックを使用する必要がありません。システムの負荷が増えるだけで、並列処理も減少します。トランザクションがデータの行をロックした場合、他のトランザクションは、行の数を処理する前にトランザクションが完了するのを待つ必要があります。

 

システムは最大でいくつのスレッドを作成できますか?(通常、実際の測定が優先されますが、テスト結果は、毎回開発されるスタックのサイズによって異なります)。

ダイレクトラインは、コマンドビューに    は、/ proc / sys / kernel /猫のスレッド-MAX   私のコンピュータのディスプレイが7572であります

もう1つは、ユーザー空間3Gのサイズを計算することです。これは、3072M / 8Mスタックスペース= 380です。     

3番目のプログラム:32754まで実行(理論値32768)

115件の元の記事を公開 29のような 訪問者50,000以上

おすすめ

転載: blog.csdn.net/huabiaochen/article/details/105019191