メインコンテンツ

このページの内容は最新ではありません。最新版の英語を参照するには、ここをクリックします。

CERT C++: CON43-C

Do not allow data races in multithreaded code

説明

このチェッカーは、既定の Polyspace® as You Code 解析では非アクティブにされますPolyspace as You Code 解析で非アクティブにされるチェッカー (Polyspace Access)を参照してください

ルール定義

マルチスレッド コード内でデータ レースを許可しないようにします。1

Polyspace 実装

ルール チェッカーは、"データ レース" をチェックします。

チェッカーの拡張

ルール チェッカーが問題を検出できるようにするには、スレッドの作成と共有変数の保護のために Polyspace が認識する同時実行プリミティブの 1 つをコードで使用する必要があります。あるいは、プロジェクト構成でタスク、割り込み、クリティカル セクション、およびその他のマルチタスク オプションを明示的に指定する必要があります。マルチタスキングを参照してください。

このチェッカーは次の方法で拡張できます。

すべて展開する

問題

データ レースは次の場合に発生します。

この欠陥を検出するには、解析前にマルチタスキング オプションを指定しなくてはなりません。[構成] ペインで [マルチタスキング] を選択してこれらのオプションを指定します。詳細は、Polyspace マルチタスキング解析の手動設定を参照してください。

リスク

異なるタスクでの操作の順序は制御されないため、データ レースにより共有変数が予測不能な値になる可能性があります。

2 つの書き込み操作間のデータ レースは、書き込み操作と読み取り操作間のデータ レースよりも深刻です。2 つの書き込み操作が相互に干渉し、不確定な値になる可能性があります。書き込みどうしの競合を特定するには、[結果のリスト] ペインの [詳細] 列でフィルターを使用します。これらの競合について、[詳細] 列に次の追加行が表示されます。

 Variable value may be altered by write-write concurrent access.
Polyspace デスクトップ ユーザー インターフェイスでの結果のフィルター処理とグループ化またはPolyspace Access Web インターフェイスでの結果のフィルタリングと並べ替え (Polyspace Access)を参照してください。

修正方法

この欠陥を修正するには、クリティカル セクション、時間的に排他、または別の方法を使用して、共有変数に対する操作を保護します。マルチタスキング コードでの共有変数の保護を参照してください。

再利用できる既存の保護を特定するには、結果に関連付けられている表とグラフを確認します。表では競合する呼び出しの各ペアが示されます。[アクセス保護] 列には、その呼び出しについての既存の保護が表示されます。競合につながる関数呼び出しの順序を確認するには、 アイコンをクリックします。以下の例を参照してください。

例 - グローバル変数に対する複数のタスクからの保護されない操作



int var; //Noncompliant
void begin_critical_section(void);
void end_critical_section(void);

void increment(void) {
    var++; 
}

void task1(void)  { 
      increment();
}

void task2(void)  { 
      increment();
}

void task3(void)  { 
     begin_critical_section();
     increment();
     end_critical_section();
}

この例では、マルチタスクの動作をエミュレートするため、以下のオプションを指定しなければなりません。

オプション仕様
マルチタスクを手動で構成
タスク (-entry-points)

task1

task2

task3

クリティカル セクション詳細 (-critical-section-begin -critical-section-end)開始ルーチン終了ルーチン
begin_critical_sectionend_critical_section

コマンド ラインでは以下を使用できます。

 polyspace-bug-finder 
   -entry-points task1,task2,task3
   -critical-section-begin begin_critical_section:cs1
   -critical-section-end end_critical_section:cs1

この例では、task1task2 および task3 のタスクが関数 increment を呼び出します。incrementvar++ 操作を含み、次のような複数のマシン命令に関連します。

  • var の読み込み。

  • 増加した値の var への書き込み。

これらのマシン命令は、task1 および task2 から実行する場合、予想できない順序で同時発生が起こりえます。たとえば、task1 から var の読み込みは task2 から var への書き込み前、後どちらでも起こりえます。したがって、var の値は予測できません。

task3 はクリティカル セクションの内部で increment を呼び出しますが、他のタスクは同じクリティカル セクションを使用していません。task3 のクリティカル セクション内の操作は、他のタスクでの操作と相互に排他的ではありません。

したがって、3 つのタスクが共通の保護が行われていない共有変数に対して操作を行っています。結果の詳細で、競合する関数呼び出しの各ペアが表示されます。

A snapshot of the Result Details pane showing operations on global variables in several pairs of threads or tasks. The Access Protections column shows whether the operations are protected from concurrent access.

アイコンをクリックすると、読み取りまたは書き込み操作のエントリ ポイントから始まる関数呼び出し順序が表示されます。task3 から始まる操作がクリティカル セクションに含まれていることも確認できます。[アクセス保護] エントリでは、クリティカル セクションを開始、終了するロック関数とロック解除関数が表示されます。この例では、関数 begin_critical_sectionend_critical_section が表示されます。

A pictorial view of operations on a global variable in a pair of threads or tasks.

修正 —クリティカル セクションに操作を配置

考えられる 1 つの修正方法として、クリティカル セクション内に操作を配置します。クリティカル セクションは複数の方法で実装できます。次に例を示します。

  • クリティカル セクションに var++ を配置できます。task1 がクリティカル セクションに入るとき、他のタスクは task1 がクリティカル セクションを離れるまで入ることができません。3 つのタスクからの var++ 操作は互いに干渉できません。

    関数 increment でクリティカル セクションを実装するために、var++ 操作を begin_critical_section および end_critical_section の呼び出しの間に配置します。

    
    
    
    int var;
    
    void begin_critical_section(void);
    void end_critical_section(void);
    
    void increment(void) {
          begin_critical_section();
          var++;
          end_critical_section(); 
    }
    
    void task1(void)  { 
          increment();
    }
    
    void task2(void)  { 
          increment();
    }
    
    void task3(void)  { 
          increment();
    }
    

  • 3 つのタスクで、同じクリティカル セクション内に increment の呼び出しを配置できます。task1 がクリティカル セクションに入るとき、他のタスクは task1 がクリティカル セクションを離れるまで入ることができません。3 つのタスクからの increment の呼び出しは互いに干渉できません。

    クリティカル セクションを実装するには、3 つのそれぞれのタスク内で begin_critical_sectionend_critical_section の呼び出しの間に increment を呼び出します。

    
    
    
    int var;
    
    void begin_critical_section(void);
    void end_critical_section(void);
    
    void increment(void) {
          var++;       
    }
    
    void task1(void)  { 
         begin_critical_section();
         increment();
         end_critical_section();
    }
    
    void task2(void)  { 
         begin_critical_section();
         increment();
         end_critical_section();
    }
    
    void task3(void)  { 
         begin_critical_section();
         increment();
         end_critical_section();
    }

修正 — タスクを時間的に排他にする

別の修正方法として、タスク task1task2 および task3 を時間的に排他にします。時間的に排他なタスクは同時に実行することはできません。

[構成] ペインで、以下の追加オプションを指定します。

コマンド ラインでは以下を使用できます。

 polyspace-bug-finder 
     -temporal-exclusions-file "C:\exclusions_file.txt"
ここで、ファイル C:\exclusions_file.txt には以下の行があります。
task1 task2 task3

例 - pthread_create を使用して作成されたスレッドでの保護されない操作
#include <pthread.h>

pthread_mutex_t count_mutex;
long long count; //Noncompliant


void* increment_count(void* args)
{
    count = count + 1;
    return NULL;
}

void* set_count(void *args)
{
    long long c;
    c = count;
    return NULL;
}

int main(void)
{
    pthread_t thread_increment;
    pthread_t thread_get;

    pthread_create(&thread_increment, NULL, increment_count, NULL);
    pthread_create(&thread_get, NULL, set_count, NULL);
    
    pthread_join(thread_get, NULL);
    pthread_join(thread_increment, NULL);

    return 1;
}

この例では、Bug Finder は pthread_create を使用した個別のスレッドの作成を検出します。ID が thread_increment であるスレッドでの演算 count = count + 1 と、ID が thread_get であるスレッドでの演算 c = count が競合するため、[データ レース] 欠陥が報告されます。変数 count は一般的な保護が使用されておらず、複数のスレッドからアクセスされます。

この 2 つの競合する操作は非アトミックです。32 ビット ターゲットでの演算 c = count は非アトミックです。マルチタスキング コードでのアトミック操作の定義を参照してください。

修正 — pthread_mutex_lockpthread_mutex_unlock のペアによる操作の保護

変数 count に対する同時アクセスを防ぐために、クリティカル セクションを使用して count に対する操作を保護します。関数 pthread_mutex_lock と関数 pthread_mutex_unlock を使用して、クリティカル セクションを実装します。

#include <pthread.h>

pthread_mutex_t count_mutex;
long long count;


void* increment_count(void* args)
{
    pthread_mutex_lock(&count_mutex);
    count = count + 1;
    pthread_mutex_unlock(&count_mutex);
    return NULL;        
}

void* set_count(void *args)
{
    long long c;
    pthread_mutex_lock(&count_mutex);
    c = count;
    pthread_mutex_unlock(&count_mutex);
    return NULL;
}

int main(void)
{
    pthread_t thread_increment;
    pthread_t thread_get;

    pthread_create(&thread_increment, NULL, increment_count, NULL);
    pthread_create(&thread_get, NULL, set_count, NULL);

    pthread_join(thread_get, NULL);
    pthread_join(thread_increment, NULL);

    return 1;
}

チェック情報

グループ: 10.同時実行 (CON)

バージョン履歴

R2019a で導入

すべて展開する


1 This software has been created by MathWorks incorporating portions of: the “SEI CERT-C Website,” © 2017 Carnegie Mellon University, the SEI CERT-C++ Web site © 2017 Carnegie Mellon University, ”SEI CERT C Coding Standard – Rules for Developing safe, Reliable and Secure systems – 2016 Edition,” © 2016 Carnegie Mellon University, and “SEI CERT C++ Coding Standard – Rules for Developing safe, Reliable and Secure systems in C++ – 2016 Edition” © 2016 Carnegie Mellon University, with special permission from its Software Engineering Institute.

ANY MATERIAL OF CARNEGIE MELLON UNIVERSITY AND/OR ITS SOFTWARE ENGINEERING INSTITUTE CONTAINED HEREIN IS FURNISHED ON AN "AS-IS" BASIS. CARNEGIE MELLON UNIVERSITY MAKES NO WARRANTIES OF ANY KIND, EITHER EXPRESSED OR IMPLIED, AS TO ANY MATTER INCLUDING, BUT NOT LIMITED TO, WARRANTY OF FITNESS FOR PURPOSE OR MERCHANTABILITY, EXCLUSIVITY, OR RESULTS OBTAINED FROM USE OF THE MATERIAL. CARNEGIE MELLON UNIVERSITY DOES NOT MAKE ANY WARRANTY OF ANY KIND WITH RESPECT TO FREEDOM FROM PATENT, TRADEMARK, OR COPYRIGHT INFRINGEMENT.

This software and associated documentation has not been reviewed nor is it endorsed by Carnegie Mellon University or its Software Engineering Institute.