EN JP CN

CONC.DL

CONC.DL

デッドロック

マルチスレッドプログラムでのデッドロックとは、2 つ以上のスレッドが互いにブロックし合い、それぞれが他方が終了するのを待機する状況を指します。これは一般に、各スレッドが他のスレッドが必要とするリソースを保持し、各スレッドがそのリソースを必要とするまでどれもブロック解除ができない状況の、異常なロック競合によって生じます。

デッドロックは一般には、異常に予約されたロックの循環チェーンによって起こります。たとえば、Thread 1 は、ロック B の前に予約されたロック A を呼び出すロック予約パターンを持つコードを実行します。一方、Thread 2 は (同じである可能性のある) コードを実行しますが、これはロック A の前に予約されたロック B を呼び出す別のロック予約パターンを持っています。これら 2 つのスレッドが実行で競合すると、Thread 2 がすでに予約した後で、Thread 1 がロック B を要求する可能性があります。Thread 2 は、Thread 1 がすでに予約済みのため、ロック A を単にブロックする目的だけで要求する可能性があります。

チェッカー CONC.DL はデッドロックのインスタンスを検出します。

脆弱性とリスク

この種の循環ロック参照はプログラムの行き詰まり、UI がアクティブにならない、デバイスが応答しない、などの問題を引き起こす可能性があります。現場でのこのような問題のデバッグにより、開発者は数週間の時間を浪費し、顧客にはイライラだけが残ります。

軽減と防止

ロック競合を回避するための指針:

  • コードのロックされるセクションを、できるだけ小さく、できるだけ単純にして、理解しやすいようにします。
  • データ競合のような同時実行問題を起こす可能性のあるコードのセクションをロックしないこと。
  • 循環待ち条件は、是が非でも避けること。
  • いくつかのロックを、通常はエスカレートしたガードパターンで使用する場合、環境ごとに厳密に同じエスカレーションが実行されることを必ず確認する。

脆弱コード例 1

     1  #include <stdio.h>
     2  #include <pthread.h>
     3  static pthread_mutex_t A, B;
     4
     5  void *printmsg1(void *msg) {
     6      pthread_mutex_lock(&A);  // Execution step 1
     7      pthread_mutex_lock(&B);  // Execution step 3
     8      printf("printmsg1\n");
     9      pthread_mutex_unlock(&B);
    10      pthread_mutex_unlock(&A);
    11      return 0;
    12  }
    13
    14  void *printmsg2(void *msg) {
    15      pthread_mutex_lock(&B);  // Execution step 2
    16      pthread_mutex_lock(&A);
    17      printf("printmsg2\n");
    18      pthread_mutex_unlock(&A);
    19      pthread_mutex_unlock(&B);
    20      return 0;
    21  }
    22
    23  int main(int argc, char**argv) {
    24      pthread_t pt1, pt2;
    25      pthread_mutex_init(&A, NULL);
    26      pthread_mutex_init(&B, NULL);
    27      pthread_create(&pt1,0, printmsg1, NULL);
    28      pthread_create(&pt2,0, printmsg2, NULL);
    29      pthread_join(pt1,0);
    30      pthread_join(pt2,0);
    31      pthread_mutex_destroy(&A);
    32      pthread_mutex_destroy(&B);
    33      return 0;
    34  }

Klocwork は 6 行目で、グローバルなミューテックス (相互排除) 'A' と'B' を使用する 2 つのスレッド間でのデッドロックの可能性をレポートします。メインルーチンは、関数 'printmsg1' と関数 'printmsg2' で定義されている 2 つのスレッドを作成し、開始します。1 番目のスレッドは、ミューテックス 'A' のロックを維持したまま、7 行目でミューテックス 'B' のロックを獲得するために待機します。2 番目のスレッドは、ミューテックス 'B' のロックを維持したまま、16 行目でミューテックス 'A' のロックを獲得するために待機します。2 つのスレッド間での、このロックの循環チェーンはデッドロックの原因になります。

修正コード例 1

     1  #include <stdio.h>
     2  #include <pthread.h>
     3  static pthread_mutex_t A, B;
     4
     5  void *printmsg1(void *msg) {
     6      pthread_mutex_lock(&A);
     7      pthread_mutex_lock(&B);
     8      printf("printmsg1\n");
     9      pthread_mutex_unlock(&B);
    10      pthread_mutex_unlock(&A);
    11      return 0;
    12  }
    13
    14  void *printmsg2(void *msg) {
    15      pthread_mutex_lock(&A);
    16      pthread_mutex_lock(&B);
    17      printf("printmsg2\n");
    18      pthread_mutex_unlock(&B);
    19      pthread_mutex_unlock(&A);
    20      return 0;
    21  }
    22
    23  int main(int argc, char**argv) {
    24      pthread_t pt1, pt2;
    25      pthread_mutex_init(&A, NULL);
    26      pthread_mutex_init(&B, NULL);
    27      pthread_create(&pt1,0, printmsg1, NULL);
    28      pthread_create(&pt2,0, printmsg2, NULL);
    29      pthread_join(pt1,0);
    30      pthread_join(pt2,0);
    31      pthread_mutex_destroy(&A);
    32      pthread_mutex_destroy(&B);
    33      return 0;
    34  }

各プロセスでの同期化に複数のミューテックスを使用する場合、ミューテックスは同じ順番でロックする必要があります。修正したコード例では、15、16、18、および 19 行で、ロックとロック解除の順番が 'printmsg2' と'printmsg1' とで同じになるように変更され、これによって循環チェーンが絶対に発生しないようになっています。

脆弱コード例 2

     1  #include <stdio.h>
     2  #include <pthread.h>
     3  static pthread_mutex_t A, B;
     4  static pthread_cond_t cond;
     5  static int count;
     6  
     7  void *f1(void *msg) {
     8      pthread_mutex_lock(&A);  
     9      pthread_mutex_lock(&B);
    10      while (count) {
    11          pthread_cond_wait(&cond, &B);
    12      }  
    13      pthread_mutex_unlock(&B);
    14      pthread_mutex_unlock(&A);
    15      return 0;
    16  }   
    17  void *f2(void *msg) {
    18      pthread_mutex_lock(&A);
    19      pthread_mutex_lock(&B);
    20      count--;
    21      pthread_cond_broadcast(&cond);
    22      pthread_mutex_unlock(&B);
    23      pthread_mutex_unlock(&A);
    24      return 0;
    25  }   
    26      
    27  int main(int argc, char**argv) {
    28      pthread_t pt1, pt2;
    29      pthread_mutex_init(&A, NULL);
    30      pthread_mutex_init(&B, NULL);
    31      pthread_cond_init(&cond, NULL);
    32      count = 1;
    33      pthread_create(&pt1,0, f1, NULL);
    34      pthread_create(&pt2,0, f2, NULL);
    35  
    36      pthread_join(pt1, NULL);
    37      pthread_join(pt2, NULL);
    38      pthread_mutex_destroy(&A);
    39      pthread_mutex_destroy(&B);
    40      pthread_cond_destroy(&cond);
    41      return 0;
    42  }

Klocwork は 11 行目で、グローバルなミューテックス (相互排除) 'A' と条件付き変数 'cond' を使用する 2 つのスレッド間でのデッドロックの可能性をレポートします。1 番目のスレッドは、ミューテックス 'A' のロックを維持したまま、11 行目で条件付き変数 'cond' を無期限に待機する可能性があります。2 番目のスレッドは、グローバル変数 'count' を変更して条件付き変数 'cond' に信号を送るために、18 行目でミューテックス 'A' のロックを獲得するために待機します。

修正コード例 2

     1  #include <stdio.h>
     2  #include <pthread.h>
     3  static pthread_mutex_t A, B;
     4  static pthread_cond_t cond;
     5  static int count;
     6  
     7  void *f1(void *msg) {
     8      pthread_mutex_lock(&B);
     9      while (count) {
    10          pthread_cond_wait(&cond, &B);
    11      }  
    12      pthread_mutex_unlock(&B);
    13      return 0;
    14  }
    15  void *f2(void *msg) {
    16      pthread_mutex_lock(&A);
    17      pthread_mutex_lock(&B);
    18      count--;
    19      pthread_cond_broadcast(&cond);
    20      pthread_mutex_unlock(&B);
    21      pthread_mutex_unlock(&A);
    22      return 0;
    23  }
    24  
    25  int main(int argc, char**argv) {
    26      pthread_t pt1, pt2;
    27      pthread_mutex_init(&A, NULL);
    28      pthread_mutex_init(&B, NULL);
    29      pthread_cond_init(&cond, NULL);
    30      count = 1;
    31      pthread_create(&pt1,0, f1, NULL);
    32      pthread_create(&pt2,0, f2, NULL);
    33  
    34      pthread_join(pt1, NULL);
    35      pthread_join(pt2, NULL);
    36      pthread_mutex_destroy(&A);
    37      pthread_mutex_destroy(&B);
    38      pthread_cond_destroy(&cond);
    39      return 0;
    40  }

条件付き待機関数の前のロックのセットには、同じ関数でロック解除される、必要なミューテックスのみを含む必要があります。修正したコード例では、待機関数 'pthread_cond_wait' の前の、必要のないミューテックス 'A' のロックが削除されました。ミューテックス 'A' のロックの削除により、10 行目のデッドロックレポートの可能性がなくなります。

関連チェッカー

拡張機能

このチェッカーは機能を拡張できます。関連するナレッジベースレコードには以下の種類があります。

詳細については、C/C++ 解析のチューニングを参照してください。