2

静的メンバー変数をフラグとして使用するクラスがあります。プログラムはマルチスレッド化されており、静的変数の値の変更はスレッド間で一貫して伝達されません。

コードは次のようになります。

MyClass.h ファイル:

class MyClass 
{
private:
    void runLoop();
    static bool shutdownRequested;
};

MyClass.cpp ファイル:

bool MyClass::shutdownRequested = false;  // static variable definition

void MyClass::runLoop()
{
    // much code omitted 

    if (isShutdownNecessary() && !shutdownRequested)
    {
        shutdownRequested = true;  // Race condition, but that's OK
        MyLog::Error("Service shutdown requested");
        // more code omitted
    }
}

上記のログ行はおそらく 1 回だけ表示されると予想していましたが、理論的には、競合状態のためにスレッドごとに 1 回だけ表示される可能性があります。(私の場合、競合状態は許容されます。) ただし、スレッドごとに何十回もログ行が表示されます。MyLog クラスはログ行ごとにスレッド ID、プロセス ID なども記録するため、わかります。

これまでのところ、この問題は Windows リリース ビルドでのみ確認されています。Windows デバッグ ビルドや Linux ビルドではまだ確認していません。

マルチコア プロセッサのさまざまなコアでさまざまなスレッドが実行されているため、ログ行がスレッドごとに 1 回表示されることは理解できます。同じスレッドがログ行を何度も実行していることに驚きました。

これを引き起こす可能性のある特定のメカニズムと、静的変数の値の更新を強制的に認識させるためにできること(同期など)を誰かが明らかにすることはできますか?

4

4 に答える 4

2

一般的に、「レースはOK」というのは決して真実ではありません。通常の変数の同時書き込みと読み取りとして定義されるデータ競合は、私が知っているすべてのスレッド モデル (Visual C++、POSIX スレッド、および C++11 を含む) では未定義の動作です。

そうは言っても、Visual C++を使用していると述べたので、共有変数を「揮発性」と宣言することで逃げることができます。 Microsoftのドキュメントには次のように書かれています:

/volatile:ms コンパイラ オプションを使用すると (既定では、ARM 以外のアーキテクチャがターゲットになっている場合)、コンパイラは、他のグローバル オブジェクトへの参照の順序を維持するだけでなく、揮発性オブジェクトへの参照間の順序を維持するための追加のコードを生成します。特に:

揮発性オブジェクトへの書き込み (揮発性書き込みとも呼ばれます) にはリリース セマンティクスがあります。つまり、命令シーケンスで揮発性オブジェクトへの書き込みの前に発生するグローバルまたは静的オブジェクトへの参照は、コンパイルされたバイナリでの揮発性書き込みの前に発生します。

揮発性オブジェクトの読み取り (揮発性読み取りとも呼ばれます) には、取得セマンティクスがあります。つまり、命令シーケンスで揮発性メモリの読み取り後に発生するグローバルまたは静的オブジェクトへの参照は、コンパイルされたバイナリでの揮発性読み取りの後に発生します。

これにより、揮発性オブジェクトをマルチスレッド アプリケーションでのメモリのロックと解放に使用できるようになります。

これにより、少なくとも動作が明確になります。複数のスレッドがすべてメッセージをログに記録できるという意味で競合状態はまだありますが、「未定義の動作」という意味での「データ競合」ではありません。

スレッドが「独自の更新を認識」しない理由については、同期がないと、スレッドはパフォーマンスのためにアドレスに「投機的に保存」する可能性があります。つまり、コンパイラは次のようなコードを発行する可能性があります。

bool tmp = shutdownRequested;
shutdownRequested = true;
if (isShutdownNecessary() && !tmp)
{
    MyLog::Error("Service shutdown requested");
    // more code omitted
}
else
    shutdownRequested = false;

isShutdownNecessary()これは、 が にアクセスしないことをコンパイラが証明できる限り、シングルスレッド プログラムの正当な変換ですshutDownRequested。コンパイラ (または CPU) は、この投機的なバージョンの方が高速であると認識している可能性があります。ただし、マルチスレッドの場合は、表示されている動作が発生する可能性があります。分解すると確実にわかります...

この種の投機的実行は、コンパイラと CPU の世代ごとにより攻撃的になる傾向があり、「データ競合」が未定義の動作を非常に具体的に引き起こす理由の 1 つです。コードが来週以降も存続する可能性がある場合は、そこに行きたくありません。

このvolatile宣言により、Visual Studio はこの種の変換を行うことができなくなります。しかし、プラットフォーム間でこれを修正する唯一の方法は、ミューテックス (およびこれがビジー ループの場合は条件変数) を使用して適切にロックすることです。これらの詳細は、C++11 より前のプラットフォーム間で異なります。

于 2012-09-26T00:30:50.460 に答える
1

最も簡単な解決策は、変数を静的な揮発性ブール値として宣言することです。volatile宣言は、変数がキャッシュされる原因となるあらゆる種類の最適化をコンパイラーが実行できないようにします。

于 2012-09-25T23:55:23.307 に答える
0

おそらく、ミューテックス+共有変数が必要です。

于 2012-09-25T22:09:56.397 に答える
0

ブーストや C++11 のアトミック機能を使用できない場合は、読み取り/書き込みロックを使用して競合状態を回避できます。これにより、mutex で発生する可能性があるロックの競合を減らすことができます。読み取り/書き込みロックは、複数の同時読み取りが存在する可能性があるため、多くの読み取りと時折の (少数の) 書き込みがある場合に特に役立ちます。書き込みに関しては、一度に 1 つしか存在できず、これも読み取りと相互に排他的です。

Linux では、pthread_rwlock_t を使用して読み取り/書き込みロックを使用できます。Windows では、次の 2 つのリファレンスを参照してください。

http://msdn.microsoft.com/en-us/library/windows/desktop/aa904937(v=vs.85).aspx

http://www.codeproject.com/Articles/16411/Ultra-simple-C-Read-Write-Lock-Class-for-Windows

于 2012-09-26T07:10:03.170 に答える