Sun Studio 12: スレッドアナライザユーザーズガイド

2.5 誤検出 (False Positive)

スレッドアナライザは、実際には発生しなかったデータ競合を報告することがときどきあります。それらを誤検出と呼んでいます。たいていの場合、誤検出は 「2.5.1 ユーザー定義の同期化機構」または 「2.5.2 異なるスレッドによって再利用されるメモリー」が原因です。

2.5.1 ユーザー定義の同期化機構

スレッドアナライザは、OpenMP 指令、POSIX スレッド、および Solaris スレッドの提供する大半の標準同期化 API および構造を認識できます。ただし、ユーザー定義の同期化機構は認識できず、コードにそうした同期化機構が含まれていると、実際にはそうではないデータ競合を報告することがあります。たとえば、スレッドアナライザは、CAS 命令や busy-waits を使用した post および wait 操作などによるロックの実装を認識できません。次に、POSIX スレッド条件変数が一般的な使われ方をしているプログラムでの誤検出データ競合の典型的な例を示します。

/* 初期状態では ready_flag は 0 */
 
/* スレッド 1: 生産者 */
100   data = ...
101   pthread_mutex_lock (&mutex);  
102   ready_flag = 1;
103   pthread_cond_signal (&cond);
103   pthread_mutex_unlock (&mutex);
...
/* スレッド 2: 消費者 */
200   pthread_mutex_lock (&mutex);
201   while (!ready_flag) {
202       pthread_cond_wait (&cond, &mutex);   
203   }
204   pthread_mutex_unlock (&mutex);
205   ... = data;

通常、pthread_cond_wait() 呼び出しは、プログラムエラーや間違った呼び起こしから保護するために、述語を評価するループの中で行われます。しばしば、述語の評価および代入は相互排他ロックで保護されます。前述のコードでは、スレッド 1 は行 100 で変数 data の値を生成し、行 102 で ready_flag の値を 1 に設定して、データが生成されたことを示します。そして、pthread_cond_signal() を呼び出して、消費者スレッドのスレッド 2 を呼び起こします。スレッド 2 は、ループ内で述語 (!ready_flag) を評価します。このスレッドは、フラグが設定されていることを検出すると、行 205 でデータを消費します。

行 102 の ready_flag の書き込みと行 201 の ready_flag の読み取りが同じ相互排他ロックで保護されているため、この 2 つのアクセスの間にデータ競合はなく、スレッドアナライザはそのことを正しく認識します。

行 100 の data の write と行 205 の data の read は、相互排他ロックによって保護されていません。しかし、プログラムのロジックでは、フラグ変数 ready_flag があるために、行 205 の read は必ず行 100 の write のあとに発生します。このため、データへのこれら 2 つのアクセスの間にはデータ競合はありません。ただし、pthread_cond_wait() への呼び出し (行 202) が実行時に実際に呼び出されなかった場合、スレッドアナライザは、この 2 つのアクセスの間にデータ競合が存在すると報告します。行 201 が実行されないうちに行 102 が実行されていて、行 201 が実行されると、ループの判定文の評価は偽となり、行 202 は実行されません。スレッドアナライザは、pthread_cond_signal() 呼び出しと pthread_cond_wait() 呼び出しを監視し、同期されていると認識するために、それらを対のものとしてみなすことができます。行 202 の pthread_cond_wait() が呼び出されない場合には、行 205 の読み取りの前に行 100 の書き込みが 必ず実行されることを認識しません。このため、同時に実行されたとみなし、両者の間にデータ競合があると報告します。

この種の誤検出データ競合をなくすために、スレッドアナライザには一群の API が用意をされており、ユーザー定義の同期化機構が実行されたことをスレッドアナライザに通知させることができます。詳細は、「A.1 スレッドアナライザのユーザー API」を参照してください。

2.5.2 異なるスレッドによって再利用されるメモリー

メモリー管理ルーチンには、別のスレッドが使用できるように、スレッドによって解放されたメモリーを再利用するものがあります。スレッドアナライザは、異なるスレッドによって使用される同じ位置のメモリーについて、使用される期間を正しく認識することができない場合があります。この時、スレッドアナライザは誤検出データ競合を報告します。次は、この種の誤検出データ競合の例です。

   /*----------*/                         /*----------*/
    /* スレッド 1 */                       /* スレッド 2 */
    /*----------*/                         /*----------*/
    ptr1 = mymalloc(sizeof(data_t));
    ptr1->data = ...
    ...
    myfree(ptr1);

                                           ptr2 = mymalloc(sizeof(data_t));   
                                           ptr2->data = ...
                                           ...
                                           myfree(ptr2);

スレッド 1 と 2 は同時に実行され、それぞれ専用メモリーとして使用する大量のメモリーを割り当てます。ここで、mymalloc() ルーチンは、直前の myfree() の呼び出しによって解放されたメモリーを供給すると仮定します。スレッド 1 が myfree() を呼び出す前にスレッド 2 が mymalloc() を呼び出した場合、ptr1ptr2 は異なる値を取得し、2 つのスレッドの間にデータ競合はありません。ただし、スレッド 1 が myfree() を呼び出したあとでスレッド 2 が mymalloc() を呼び出した場合は、ptr1ptr2 は同じ値を持つ可能性があります。スレッド 1 はそのメモリーにアクセスしなくなっているため、データ競合はありません。しかし、mymalloc() がメモリーを再利用していることを認識していない場合、スレッドアナライザは ptr1 データの write と ptr2 データの書き込みの間にデータ競合があると報告します。この種の誤検出は、C++ アプリケーションで C++ ランタイムライブラリが一時変数用のメモリーを再利用する場合に起こることがよくあります。また、独自のメモリー管理ルーチンを実装しているユーザーアプリケーションでもよく起きます。現状では、スレッドアナライザは標準の malloc()calloc()、および realloc() インタフェースを使用して実行されるメモリーの割り当てと解放操作を認識できます。