Polyspace でのスレッド作成とクリティカル セクションの自動検出
Polyspace® を使用して、複数のスレッドを同時実行するプログラムを解析できます。Polyspace では、解析でコード内に同時実行モデルが認識される場合、マルチタスキング コードのデータ レース、デッドロック、その他の同時実行の欠陥を解析できます。場合によっては、Polyspace で、コード内のスレッド作成とクリティカル セクションを自動的に検出できます。Bug Finder では、それらを既定で検出します。Code Prover では、オプション [Code Prover の同時実行の自動検出を有効にする] (-enable-concurrency-detection) を使用して、自動検出を有効にします。
マルチタスキング コード解析のワークフローについては、Polyspace でのマルチタスキング プログラムの解析を参照してください。
スレッド作成関数が自動的に検出されない場合:
この関数を Polyspace で自動的に検出できるスレッド作成関数にマップすることもできます。オプション
-code-behavior-specificationsを使用します。そうしない場合は、マルチタスキング スレッドのモデル化は、構成オプションを使用して手動で行わなければなりません。Polyspace マルチタスキング解析の手動設定を参照してください。
Polyspace で検出できるマルチタスキング ルーチン
Polyspace では、これらのグループのプリミティブを使用する場合、スレッド作成とクリティカル セクションを検出できます。Polyspace では、これらのルーチンの呼び出しは、新しいスレッドの作成、またはクリティカル セクションの開始や終了として認識します。
POSIX
スレッド作成: pthread_create
クリティカル セクション開始: pthread_mutex_lock
クリティカル セクション終了: pthread_mutex_unlock
VxWorks
スレッド作成: taskSpawn
クリティカル セクション開始: semTake
クリティカル セクション終了: semGive
VxWorks® の同時実行のプリミティブ型の自動検出を有効にするには、Polyspace デスクトップ製品のユーザー インターフェイスで、VxWorks テンプレートを使用します。テンプレートについての詳細は、構成テンプレートを使用した Polyspace デスクトップ ユーザー インターフェイスでのプロジェクトの作成を参照してください。コマンド ラインで、以下のオプションを使用します。
-D1=CPU=I80386 -D2=__GNUC__=2 -D3=__OS_VXWORKS
マルチタスキング関数が main という名前のエントリ ポイントから作成されている場合のみ、同時実行を検出できます。エントリ ポイントが vxworks_entry_point などの別の名前の場合、以下のいずれかを実行します。
main関数を用意します。プリプロセッサ定義 (-D)プリプロセッサ定義で、vxworks_entry_point=mainを設定します。
Windows
スレッド作成: CreateThread
クリティカル セクション開始: EnterCriticalSection
クリティカル セクション終了: LeaveCriticalSection
μC/OS II
スレッド作成: OSTaskCreate
クリティカル セクション開始: OSMutexPend
クリティカル セクション終了: OSMutexPost
μC/OS III
スレッド作成: OSTaskCreate
クリティカル セクション開始: OSMutexPend
クリティカル セクション終了: OSMutexPost
C++11
スレッド作成: std::thread::thread
クリティカル セクション開始: std::mutex::lock または std::mutex::try_lock
クリティカル セクション終了: std::mutex::unlock
信号ハンドラー: std::signal
C++11 スレッドの自動検出には、コンパイラ ヘッダー ファイルのパスを明示的に指定するか、polyspace-configure を使用します。
たとえば、スレッド作成に std::thread を使用する場合、thread.h を含むフォルダーへのパスを明示的に指定します。
スレッドの自動検出の制限も参照してください。
C11
スレッド作成: thrd_create
クリティカル セクション開始: mtx_lock
クリティカル セクション終了: mtx_unlock
信号ハンドラー: signal
自動スレッド検出の例
次のマルチタスキング コードでは、5 本のフォークを共有する 5 人の哲学者がモデル化されています。この例では、POSIX® スレッド作成ルーチンを使用して、デッドロックの典型的な例を示します。次のコードに対して Bug Finder を実行し、デッドロックを確認します。
#include "pthread.h"
#include <stdio.h>
#include <unistd.h>
pthread_mutex_t forks[5];
void* philo1(void* args)
{
while (1) {
printf("Philosopher 1 is thinking\n");
sleep(1);
pthread_mutex_lock(&forks[0]);
printf("Philosopher 1 takes left fork\n");
pthread_mutex_lock(&forks[1]);
printf("Philosopher 1 takes right fork\n");
printf("Philosopher 1 is eating\n");
sleep(1);
pthread_mutex_unlock(&forks[1]);
printf("Philosopher 1 puts down right fork\n");
pthread_mutex_unlock(&forks[0]);
printf("Philosopher 1 puts down left fork\n");
}
return NULL;
}
void* philo2(void* args)
{
while (1) {
printf("Philosopher 2 is thinking\n");
sleep(1);
pthread_mutex_lock(&forks[1]);
printf("Philosopher 2 takes left fork\n");
pthread_mutex_lock(&forks[2]);
printf("Philosopher 2 takes right fork\n");
printf("Philosopher 2 is eating\n");
sleep(1);
pthread_mutex_unlock(&forks[2]);
printf("Philosopher 2 puts down right fork\n");
pthread_mutex_unlock(&forks[1]);
printf("Philosopher 2 puts down left fork\n");
}
return NULL;
}
void* philo3(void* args)
{
while (1) {
printf("Philosopher 3 is thinking\n");
sleep(1);
pthread_mutex_lock(&forks[2]);
printf("Philosopher 3 takes left fork\n");
pthread_mutex_lock(&forks[3]);
printf("Philosopher 3 takes right fork\n");
printf("Philosopher 3 is eating\n");
sleep(1);
pthread_mutex_unlock(&forks[3]);
printf("Philosopher 3 puts down right fork\n");
pthread_mutex_unlock(&forks[2]);
printf("Philosopher 3 puts down left fork\n");
}
return NULL;
}
void* philo4(void* args)
{
while (1) {
printf("Philosopher 4 is thinking\n");
sleep(1);
pthread_mutex_lock(&forks[3]);
printf("Philosopher 4 takes left fork\n");
pthread_mutex_lock(&forks[4]);
printf("Philosopher 4 takes right fork\n");
printf("Philosopher 4 is eating\n");
sleep(1);
pthread_mutex_unlock(&forks[4]);
printf("Philosopher 4 puts down right fork\n");
pthread_mutex_unlock(&forks[3]);
printf("Philosopher 4 puts down left fork\n");
}
return NULL;
}
void* philo5(void* args)
{
while (1) {
printf("Philosopher 5 is thinking\n");
sleep(1);
pthread_mutex_lock(&forks[4]);
printf("Philosopher 5 takes left fork\n");
pthread_mutex_lock(&forks[0]);
printf("Philosopher 5 takes right fork\n");
printf("Philosopher 5 is eating\n");
sleep(1);
pthread_mutex_unlock(&forks[0]);
printf("Philosopher 5 puts down right fork\n");
pthread_mutex_unlock(&forks[4]);
printf("Philosopher 5 puts down left fork\n");
}
return NULL;
}
int main(void)
{
pthread_t ph[5];
pthread_create(&ph[0], NULL, philo1, NULL);
pthread_create(&ph[1], NULL, philo2, NULL);
pthread_create(&ph[2], NULL, philo3, NULL);
pthread_create(&ph[3], NULL, philo4, NULL);
pthread_create(&ph[4], NULL, philo5, NULL);
pthread_join(ph[0], NULL);
pthread_join(ph[1], NULL);
pthread_join(ph[2], NULL);
pthread_join(ph[3], NULL);
pthread_join(ph[4], NULL);
return 1;
}philo1、philo2、philo3、philo4 および philo5 がそれぞれの哲学者を表現しています。各関数には、食事をするのに必要なフォーク 2 本を表現する 2 つの pthread_mutex_t リソースが必要です。5 つの関数はすべて、5 つのコンカレント スレッドで同時に実行されます。 ただし、この例ではデッドロックが発生します。それぞれの哲学者が最初のフォークを手にする (各スレッドが 1 つの pthread_mutex_t リソースをロックする) と、すべてのフォークが使用中になります。よって、哲学者 (スレッド) が 2 本目のフォーク (2 番目の pthread_mutex_t リソース) が使用可能になるのを待機します。しかし、待機中の哲学者 (スレッド) がすべてのフォーク (リソース) を持っているため、デッドロックが発生します。
自動的に検出されるスレッドの命名規則
pthread_create() などの関数を使用して新しいスレッド (タスク) を作成する場合、各スレッドは固有の識別子に関連付けられます。たとえば、以下の例では、識別子 id1 と id2 を使用して 2 つのスレッドが作成されます。
pthread_t* id1, id2;
void main()
{
pthread_create(id1, NULL, start_routine, NULL);
pthread_create(id2, NULL, start_routine, NULL);
}データ レースがスレッド間で発生する場合、解析でそれを検出できます。結果を表示すると、スレッドは task_ として示されます。ここで、idid はスレッドに関連付けられた識別子です。前の例では、スレッドは task_id1 および task_id2 として識別されます。
スレッド識別子に応じて、次のようになります。
関数にローカルな場合、スレッド名はその関数を示します。
たとえば、以下で作成されるスレッドは
task_f:idとして表示されます。void f(void) { pthread_t* id; pthread_create(id, NULL, start_routine, NULL); }構造体のフィールドの場合、スレッド名は構造体を示します。
たとえば、以下で作成されるスレッドは
task_a#idとして表示されます。struct {pthread_t* id; int x;} a; pthread_create(a.id,NULL,start_routine,NULL);配列メンバーの場合、スレッド名はその配列を示します。
たとえば、以下で作成されるスレッドは
task_tab[1]として表示されます。pthread_t* tab[10]; pthread_create(tab[1],NULL,start_routine,NULL);
異なるスレッド識別子を使用して 2 つのスレッドを作成し、それらのスレッド識別子に対して同じローカル変数名を使用した場合、2 つ目のスレッドの名前は 1 つ目のスレッドと区別するために変更されます。たとえば、以下のスレッドは task_func:id および task_func:id:1 として表示されます。
void func()
{
{
pthread_t id;
pthread_create(&id, NULL, &task, NULL);
}
{
pthread_t id;
pthread_create(&id, NULL, &task, NULL);
}
}スレッドの自動検出の制限
Polyspace によって抽出されたマルチタスキング モデルには、いくつかの機能が含まれていません。Polyspace では以下をモデル化できません。
スレッドの優先順位と属性 — Polyspace では無視。
再帰セマフォ。
非有界のスレッド識別子 (
extern pthread_t ids[]— 警告など)。高次の呼び出しによる同時実行プリミティブ型の呼び出し — 警告。
スレッド識別子のエイリアス — Polyspace でエイリアスが使用されると、過剰近似が発生します。
スレッドの終了 — Polyspace は
pthread_joinおよびthrd_joinを無視します。Polyspace はpthread_exitおよびthrd_exitを標準終了で置き換えます。(Polyspace Bug Finder™ のみ) 異なるポインター引数を使用した、同じ関数への複数回の呼び出しによる複数のスレッドの作成。
(Polyspace Code Prover™ のみ) 共有ローカル変数 — グローバル変数のみが共有と見なされます。ローカル変数に複数のスレッドからアクセスがある場合、解析ではその変数の共有特性は考慮されません。
(Polyspace Code Prover のみ) 共有動的メモリ — グローバル変数のみが共有と見なされます。動的に割り当てられたメモリ領域に複数のスレッドからアクセスがある場合、解析ではその変数の共有特性は考慮されません。
threadIdがNULLに設定されている場合にCreateThreadで作成されるタスクの数 — 同じ関数を実行する複数のスレッドを作成するとき、CreateThreadの最後の引数がNULLである場合、Polyspace はこの関数の 1 つのインスタンス (つまりタスク) のみ検出します。(C++11 のみ) スレッド作成時にラムダ式を開始関数として使用している場合、Polyspace はラムダ式内の共有変数を検出しません。
(Polyspace Code Prover での C++11 スレッドのみ) スレッド関数の引数としての文字列リテラル — スレッド関数に
std::string&パラメーターがあり、文字列リテラル引数を渡す場合、Code Prover では [不適切にデリファレンスされたポインター] のレッド エラーが表示されます。
参考
Code Prover の同時実行の自動検出を有効にする (-enable-concurrency-detection) | -code-behavior-specifications
