ライティングテスト
class Runnable {
protected:
Runnable () : thread(NULL) {
pthread_create(&thread, NULL, Runnable::Run, this);
}
~Runnable () {
if (NULL != thread) {
// Destroy thread.
pthread_join(thread, NULL);
}
}
protected:
// Override this interface which extended runnable.
virtual void Run (void) {
}
private:
static void* Run (void* args) {
Runnable* runnable = static_cast<Runnable*>(args);
runnable->Run();
return NULL;
}
private:
pthread_t thread;
};
class Worker : public Runnable {
public:
Worker () : quit(false) {
}
~Worker () {
// Set quit flag.
quit = true;
}
protected:
// Override thread run function.
void Run (void) override {
while (!quit) {
// TODO somethine
}
}
private:
volatile bool quit; // Worker thread quit flag.
// OTHER member variables
};
上記のプログラムでバグを見つけてください。
ケーススタディ
上記のテスト用質問のコードは、一定の確率でクラッシュするか応答しなくなります。その理由は、C ++メンバー変数とコンストラクターの初期化順序に関連しています。C ++での変数またはオブジェクトの作成は、次の2つのステップと見なすことができます。
- タイプと同じサイズのメモリを適用します。これは基本的にmalloc(sizeof(T));と同等です。
- 要求されたメモリの値はランダムな値であるため、このタイプのコンストラクタを呼び出して、オブジェクトの初期化操作を実装します。
これ自体は、C ++コンパイラを1つに結合できるようにする、2つのステップの問題であり、プログラマーは、プログラマーの注意を払って1つのステップに単純化する可能性があります。これにより、バグを利用できるようになります。継承後のクラスのコンストラクターである場合、C ++は最初に親クラスのコンストラクターを呼び出し、次にサブクラスのコンストラクターを呼び出します。
そういえば、読者は上記のコードのバグに気付いているのだろうか?問題は次のコードにあります。
Worker () : quit(false) {
}
ワーカーはRunnableのサブクラスです。つまり、プログラムはRunnableのコンストラクターを最初に実行します。
Runnable () : thread(NULL) {
pthread_create(&thread, NULL, Runnable::Run, this);
}
上記のコードから、Runnableのコンストラクターがスレッドを作成し、Workerクラスが書き換えられたため、このスレッドの関数がWorker :: Run()を実行することがわかります。スレッドが作成された後、プログラムはワーカーのコンストラクターの実行を開始します。つまり、quitをfalseに設定します。問題はここにあります。スレッドは終了の初期化よりも優先されます。ある確率で、終了が実行された後、スレッド関数は初期化を完了していません。その結果、スレッド関数はまだ残っているため、スレッド関数は直接終了します。終了値を判断するときのランダムな値。プログラムはスレッドが作成されたと見なしますが、スレッド関数が終了し、外部マニフェストが無効になっています。
上記のバグに加えて、C ++のデストラクタの順序は、最初にサブクラスのデストラクタを実行し、次に親クラスのデストラクタを実行するというように、ワーカーのデストラクタにも問題があります。これが引き起こす問題は、Workerオブジェクトがすでにデストラクタを実行しているが、スレッド関数がまだ実行されている可能性があることです。この時点でスレッド関数がWorkerオブジェクトのメンバー変数を操作すると、崩壊するリスクがあります。
一見単純なコードですが、細部がいくつかあるため、特にCPUコアの数が少ないワークステーションではバグを見つけるのが困難です。コア数の多いサーバーでは、作成したスレッドをすぐに実行できますが、コア数が少ないとタイムスライスがないため、作成したスレッドが親スレッドより遅れて検出が困難になる可能性があります。不具合。
追加の質問
クラスには複数のメンバー変数があり、メンバー変数の初期化順序は、コンストラクターでの割り当ての順序ではなく、メンバー変数の定義時に決定されます。インターネット上にも同様のテスト用の質問があることに注意してください。
class Type {
public:
Type() :b(2), a(1){
}
private:
int a;
int b;
};
上記のコードの実際の実行順序は、aに値1が割り当てられ、bに値2が割り当てられることです。