2010-12-13 81 views
3

マルチスレッドプログラミングではあまりよくありませんので、いくつかのヘルプ/アドバイスをお願いしたいと思います。マルチスレッドアプリケーションでのアクセス違反、C++

私のアプリケーションでは、共有オブジェクトにアクセスしようとするスレッドが2つあります。 別のオブジェクト内から関数を呼び出そうとする2つのタスクについて考えることができます。わかりやすくするために、私はプログラムのいくつかの部分を示しますが、それはあまり関連性がないかもしれませんが、うまくいけば私の問題をより良くすることができます。

以下のサンプルコードを見てください:

iDataLinkLayerはDataLinkLayerオブジェクトのisntanceに純粋仮想関数および参照(ポインタ)の宣言を含むインタフェース(任意実装せずに抽象クラス)である
//DataLinkLayer.h 
class DataLinkLayer: public iDataLinkLayer { 

public: 
DataLinkLayer(void); 
~DataLinkLayer(void); 
}; 

を(dataLinkLayer)。

// DataLinkLayer.cpp 
#include "DataLinkLayer.h" 

DataLinkLayer::DataLinkLayer(void) { 

/* In reality task constructors takes bunch of other parameters 
but they are not relevant (I believe) at this stage. */ 
dll_task_1* task1 = new dll_task_1(this); 
dll_task_2* task2 = new dll_task_2(this); 

/* Start multithreading */ 
task1->start(); // task1 extends thread class 
task2->start(); // task2 also extends thread class 
} 

/* sample stub functions for testing */ 
void DataLinkLayer::from_task_1() { 
printf("Test data Task 1"); 
} 

void DataLinkLayer::from_task_2() { 
printf("Test data Task 2"); 
} 

タスク1の実装は以下のとおりです。 dataLinLayerインターフェース(iDataLinkLayer)ポインターは、dataLinkLayerの中から必要な機能にアクセスできるように、クラスcosInstructorに渡されます。

//data_task_1.cpp 
#include "iDataLinkLayer.h" // interface to DataLinkLayer 
#include "data_task_1.h" 

dll_task_1::dll_task_1(iDataLinkLayer* pDataLinkLayer) { 
this->dataLinkLayer = pDataLinkLayer; // dataLinkLayer declared in dll_task_1.h 
} 

// Run method - executes the thread 
void dll_task_1::run() { 
// program reaches this point and prints the stuff 
this->datalinkLayer->from_task_1(); 
} 
// more stuff following - not relevant to the problem 
... 

とタスク2 simialrlyなります

//data_task_2.cpp 
#include "iDataLinkLayer.h" // interface to DataLinkLayer 
#include "data_task_2.h" 

dll_task_2::dll_task_2(iDataLinkLayer* pDataLinkLayer){ 
this->dataLinkLayer = pDataLinkLayer; // dataLinkLayer declared in dll_task_2.h 
} 

// // Run method - executes the thread 
void dll_task_2::run() { 
// ERROR: 'Access violation reading location 0xcdcdcdd9' is signalled at this point 
this->datalinkLayer->from_task_2(); 
} 
// more stuff following - not relevant to the problem 
... 

私が正しく理解してように、私は2つの異なるスレッド(タスク)から共有ポインタにアクセスし、それが許可されていません。 しかし、私はオブジェクトにアクセスすることができると思っていましたが、結果は予期しないものになる可能性があります。

dll_task_2がDataLinkLayerへのポインタを使って関数を呼び出そうとすると、何かがひどく間違っているようです。 dll_task_2は優先度が低く、その後に開始されます。なぜ私はまだオブジェクトにアクセスできないのか分かりません... mutexを使って変数をロックすることができますが、主な理由は変数/オブジェクトを保護することだと思いました。

私はMicrosoft Visual C++ 2010 Expressを使用しています。 私はマルチスレッドについてあまりよく知らないので、この問題のより良い解決策を提案し、問題の理由を説明することもできます。

+3

2番目のスレッドが実行される前に、プログラムがDataLinkLayerオブジェクトを破棄している可能性があります。あなたは 'main()'のように見えますか? –

+0

'this-> datalinklayer'はおそらく無効なアドレスを含んでいます。デバッガでその値を確認できますか? – casablanca

+0

スレッドを生成するコードはありません。 –

答えて

3

アクセス違反のアドレスが0xcdcdcdcd

Wikipediaからのオフセットは非常に小さい正であると言う:マイクロソフトのC++のデバッグランタイムライブラリで使用

CDCDCDCDを初期化されていないヒープメモリをマークする。

Here is the relevant MSDN page

The corresponding value after free is 0xddddddddです。したがって、使用後フリーではなく不完全な初期化が行われる可能性があります。

EDIT:Jamesが最適化がバーチャルファンクションコールを混乱させる可能性があると尋ねました。基本的には、現在標準化されているC++メモリモデルはスレッド化を保証していないからです。 C++標準では、コンストラクタ内で行われた仮想呼び出しは、オブジェクトの最終的な動的型ではなく、現在実行されているコンストラクタの宣言型を使用することを定義しています。つまり、C++の逐次実行メモリモデルの観点からは、コンストラクタが実行を開始する前に、仮想呼び出しメカニズム(実際にはvテーブルポインタ)を設定する必要があります(具体的な点は、ベースサブオブジェクトの作成ctor-initializer-listのメンバーサブオブジェクトの構築の前)。

二つのものがスレッドシナリオで観察動作が異なるようにするために発生する可能性が今、:

まず、コンパイラは、C++逐次実行モデルでは、そのままであれば作用する任意の最適化を実行して自由ですルールが守られていた。たとえば、コンストラクター内で仮想呼び出しが行われていないことをコンパイラーが証明できれば、v-tableポインターを開始時ではなくコンストラクター本体の最後に待機して設定できます。コンストラクタがthisポインタを出力しない場合、コンストラクタの呼び出し側もポインタのコピーをまだ受け取っていないので、コンストラクタによって呼び出された関数のどれもオブジェクトに(仮想的または静的に)コールバックすることはできません工事中。しかし、コンストラクタはthisポインタを与えません。

もっと見る必要があります。このポインタが与えられた関数がコンパイラ(すなわち、現在のコンパイルユニットに含まれている)から見える場合、コンパイラはその動作を解析に含めることができる。この質問では、この関数(コンストラクタとメンバ関数のclass task)は与えられていませんでしたが、そのポインタはコンストラクタの外部から到達できないサブオブジェクトに格納されている可能性が高いようです。 サブオブジェクトのアドレスをライブラリCreateThreadの関数に渡したため、到達可能であり、それを介してメインオブジェクトにアクセス可能です」と泣き叫ぶ。ああ、あなたは "厳密なエイリアシングルール"の謎を理解していません。そのライブラリ関数は、タイプtask *のパラメータを受け付けません。そして、型がおそらくintptr_tであるが、間違いなくtask *でもchar *でもない場合、コンパイラは、if-if最適化の目的で、taskオブジェクトを指していることを前提とすることができます。また、taskオブジェクトを指しておらず、thisポインタが格納されている唯一の場所がtaskメンバサブオブジェクトの場合、仮想呼び出しをthisにするためには使用できないため、コンパイラは仮想呼び出しの設定を正当に遅延させる可能性があります機構。

しかし、それだけではありません。コンパイラが予定通りに仮想呼び出しメカニズムを設定しても、CPUメモリモデルは変更が現在のCPUコアで認識できることのみを保証します。書き込みは、完全に異なる順序で他のCPUコアから見えるようになることがあります。今、ライブラリ作成スレッド関数は、CPU書き換えの順序を制約するメモリバリアを導入すべきですが、Kozの回答がクリティカルセクション(メモリバリアを含む)を導入すると、その動作が変更されるという事実は、おそらくメモリバリアは元のコード。

そして、CPU書き込み再順序付けは、vテーブルポインタを遅らせるだけでなく、このポインタをtaskサブオブジェクトに格納することを遅延させることができます。

"マルチスレッドプログラミングは難しい"という洞窟の小さなコーナーのこのガイド付きツアーを楽しんでください。

+0

詳細な説明をいただきありがとうございます。 – James

1

printfではない、アフリカ、スレッドセーフです。クリティカルセクションでprintfを囲みます。

これを行うには、InitializeCriticalSection iDataLinkLayerクラス内にあります。次にprintfの周りにEnterCriticalSectionLeaveCriticalSectionが必要です。これにより、両方の関数が同時にprintfに入るのを防ぎます。

編集:このコードを変更してみてください:

dll_task_1* task1 = new task(this); 
dll_task_2* task2 = new task(this); 

イム

dll_task_1* task1 = new dll_task_1(this); 
dll_task_2* task2 = new dll_task_2(this); 

にそのタスクを推測は、実際にdll_task_1とdll_task_2の基底クラスである...そう、何よりも、イムそれが驚いた...。

+0

その前にエラーがあるようです。 – casablanca

+0

@Casablanca:更新された私の答え – Goz

+2

@Goz - 不完全な情報の光の中での良い提案 - 生のクリティカルセクションよりも例外安全なlock_guardクラスを使用する方が良いですか? –

0

コンストラクタの終わりの前に 'this'(つまりメンバ関数を呼び出す)を使うのは必ずしも安全ではないと思います。そのタスクは、DataLinkLayerコンストラクターの終了前にDataLinkLayerのメンバー関数を呼び出すことができます。このメンバ関数は仮想である場合は特に: http://www.parashift.com/c++-faq-lite/ctors.html#faq-10.7

+0

派生したクラスのコンストラクタ本体が始まる前に、ベースサブオブジェクトが完全に構築されています。これはほぼ確実に仮想関数呼び出しと関係があり、順次実行モデルで有効な仮想テーブル設定ロジックの最適化と組み合わされています。 –

+0

@Ben:あなたは理論的に、そしてこの小さなプログラム仮想関数呼び出しに関して、これを混乱させることができるのでしょうか?私は本当にこのことについてもっと知りたいのは興味があります。この段階では、どのように物事を乱すことができないのか分かりません。 Btw、iDataLinkLayerインターフェイスは次のようになります: 'class iDataLinkLayer {public:virtual void from_task_1(void)= 0;仮想void from_task_2(void)= 0; }; '私の前の投稿のDataLinkLayerでは、これらの機能が実装されています。ご意見ありがとうございます! – James

+0

@ジェームス:そのような説明は十分に長く、私はそれを私の答えに加えなければならないだろう。 –

0

私はDataLinkLayerの作成についてコメントしたかったのです。私は、メインからDataLinkLayerコンストラクタを呼び出すと

int main() { 
DataLinkLayer* dataLinkLayer = new DataLinkLayer(); 
while(true); // to keep the main thread running 
} 

I、coruseの、これが最初で、オブジェクトを破壊しないでください。今、DataLinkLayerコスコンストラクタ内で、私は多くの(これらの2つのタスクだけでなく)他のオブジェクトの初期化を行い、dataLinkLayerポインタのほとんどに(thisを使用して)渡します。私が関心を持つ限り、これは合法です。さらにそれを置く - それはコンパイルされ、期待どおりに実行されます。

私が興味を持ったのは、私が(もしあれば)次の全体的な考え方です。

DataLinkLayerは、パラメータを変更したり他の処理を実行しようとするいくつかのタスクによってアクセスされる親クラスです。できる限りデカップリングされたままにしておきたいので、アクセサーのインターフェイスのみを提供し、データをカプセル化して、グローバル変数、フレンド関数などを持たないようにします。

これは非常に簡単な作業でしたマルチスレッドだけが存在しないならば。私は信じて、私は途中で他の多くの落とし穴に遭遇するでしょう。

お気軽にご相談ください。

UPD:タスクにiDataLinkLayerインターフェイスポインタを渡すの

いえば - これはそれを行うには良い方法ですか? Javaでは、物事を切り離して物事をさせるために、封じ込めやいわゆる戦略パターンを実現するのはかなり普通のことです。しかし、私はそれがCで良いソリューションであるかどうか100%確信していない+ + ...それに関する任意の提案/通信?

関連する問題