26

推定:

A. WIN32 での C++。

InterlockedIncrement()B.およびを使用してインクリメントおよびデクリメントされる、適切にアラインされた揮発性整数InterlockedDecrement()

__declspec (align(8)) volatile LONG _ServerState = 0;

単純に _ServerState を読み取りたい場合、InterlockedXXX関数を介して変数を読み取る必要がありますか?

たとえば、次のようなコードを見てきました。

LONG x = InterlockedExchange(&_ServerState, _ServerState);

LONG x = InterlockedCompareExchange(&_ServerState, _ServerState, _ServerState);

目標は、 の現在の値を単純に読み取ることです_ServerState

簡単に言うことはできません:

if (_ServerState == some value)
{
// blah blah blah
}

この件については、多少の混乱があるようです。Windowsではレジスタサイズの読み取りがアトミックであることを理解しているので、このInterlockedXXX機能は不要であると思います。

マット J.


わかりました、回答ありがとうございます。ところで、これは Visual C++ 2005 および 2008 です。

それが本当なら、InterlockedXXX関数を使用して の値を読み取る必要があります_ServerState。わかりやすくするためだけに、それを行う最善の方法は何ですか?

LONG x = InterlockedExchange(&_ServerState, _ServerState);

これには、値を変更するという副作用があります。本当にやりたいのは値を読み取ることだけです。_ServerStateそれだけでなく、 の呼び出しの準備で の値がスタックにプッシュされるため、コンテキスト スイッチがある場合、フラグを間違った値にリセットする可能性がありますInterlockedExchange()

LONG x = InterlockedCompareExchange(&_ServerState, _ServerState, _ServerState);

これは、MSDN で見た例から引用しました。http://msdn.microsoft.com/en-us/library/ms686355(VS.85).aspx
を参照してください。

私が必要とするのは、次のようなものだけです。

lock mov eax, [_ServerState]

いずれにせよ、私が明確だと思ったポイントは、クリティカル セクションのオーバーヘッドを招くことなく、スレッド セーフなフラグへのアクセスを提供することです。LONG が関数ファミリーを介してこのように使用されているのを見たInterlockedXXX()ので、私の質問です。

さて、現在の値を読み取るというこの問題に対する良い解決策は次のとおりだと考えています。

LONG Cur = InterlockedCompareExchange(&_ServerState, 0, 0);
4

10 に答える 10

12

それは、「目標は _ServerState の現在の値を単に読み取ることです」という意味に依存し、使用するツールのセットとプラットフォームに依存します (Win32 と C++ を指定しますが、どの C++ コンパイラを指定するかは重要ではありません)。 .

値が破損しないように単に値を読み取りたい場合 (つまり、他のプロセッサが値を 0x12345678 から 0x87654321 に変更している場合、読み取りは 0x12344321 ではなく、これらの 2 つの値のいずれかを取得します)、単に読み取りは次のように OK です。変数が次の場合:

  • マーク付きvolatile
  • 適切に整列し、
  • プロセッサがアトミックに処理するワードサイズの単一の命令を使用して読み取る

これは C/C++ 標準では約束されていませんが、Windows と MSVC はこれらを保証しており、Win32 を対象とするほとんどのコンパイラも同様に保証していると思います。

ただし、読み取りを他のスレッドの動作と同期させたい場合は、さらに複雑になります。シンプルな「メールボックス」プロトコルがあるとします。

struct mailbox_struct {
    uint32_t flag;
    uint32_t data;
};
typedef struct mailbox_struct volatile mailbox;


// the global - initialized before wither thread starts

mailbox mbox = { 0, 0 };

//***************************
// Thread A

while (mbox.flag == 0) { 
    /* spin... */ 
}

uint32_t data = mbox.data;

//***************************

//***************************
// Thread B

mbox.data = some_very_important_value;
mbox.flag = 1;

//***************************

スレッド A は、mbox.flag が mbox.data に有効な情報があることを示すのを待ってスピンします。スレッド B はいくつかのデータを mailbox.data に書き込み、mbox.data が有効であることを示す信号として mbox.flag を 1 に設定します。

この場合、スレッド A での mbox.data の後続の読み取りがスレッド B によって書き込まれた値を取得しない場合でも、mbox.flag のスレッド A での単純な読み取りは値 1 を取得する可能性があります。

これは、コンパイラがスレッド B の mbox.data および mbox.flag への書き込みを並べ替えない場合でも、プロセッサやキャッシュが並べ替える可能性があるためです。C/C++ は、スレッド B が mbox.flag に書き込む前に mbox.data に書き込むようなコードをコンパイラが生成することを保証しますが、プロセッサとキャッシュには別の考えがある場合があります - 「メモリバリア」または「取得して取得」と呼ばれる特別な処理リリース セマンティクスを使用して、スレッドの命令ストリームのレベルより下の順序を確保する必要があります。

MSVC 以外のコンパイラが、命令レベル以下の順序付けについて主張しているかどうかはわかりません。ただし、MS は、MSVC では揮発性で十分であることを保証します。MS は、揮発性の書き込みには解放セマンティクスがあり、揮発性の読み取りにはセマンティクスの取得があると指定しています。ただし、これが適用される MSVC のバージョンはわかりません。http ://msdn.microsoft.comを参照/en-us/library/12a04hfd.aspx?ppud=4 .

インターロック API を使用して共有場所への単純な読み取りと書き込みを実行する、あなたが説明したようなコードも見ました。この問題に対する私の見解は、Interlocked API を使用することです。ロックフリーのスレッド間通信は、理解するのが非常に難しく、微妙な落とし穴がたくさんあります。バグの診断が非常に困難になる可能性のある重要なコードをショートカットしようとすることは、私には良い考えのようには思えません。 . また、Interlocked API を使用すると、コードを管理しているすべての人に「これはデータ アクセスであり、他の何かと共有または同期する必要があります。慎重に行ってください!」と叫びます。

また、Interlocked API を使用すると、ハードウェアとコンパイラの詳細を把握できなくなります - プラットフォームは、そのすべてが適切に処理されるようにします - もう不思議に思う必要はありません...

このトピックに関する有益な情報については、DDJに関する Herb Sutter の効果的な並行性に関する記事(少なくとも私にとっては、現時点ではダウンしています) を読んでください。

于 2009-04-23T04:41:50.593 に答える
9

あなたのやり方は良いです:

LONG Cur = InterlockedCompareExchange(&_ServerState, 0, 0);

私は同様のソリューションを使用しています:

LONG Cur = InterlockedExchangeAdd(&_ServerState, 0);
于 2013-11-14T15:54:03.333 に答える
5

インターロックされた命令は、原子性プロセッサ間同期を提供します。書き込みと読み取りの両方を同期する必要があるため、はい、スレッド間で共有され、ロックによって保護されていない値を読み取るには、インターロックされた命令を使用する必要があります。ロックフリープログラミング(そしてそれがあなたがしていることです)は非常にトリッキーな領域なので、代わりにロックを使用することを検討するかもしれません。これが本当に最適化する必要のあるプログラムのボトルネックの1つでない限り、

于 2009-04-23T03:19:35.720 に答える
1

32 ビットの読み取り操作は、一部の32 ビット システムではすでにアトミックです (Intel の仕様では、これらの操作はアトミックであるとされていますが、他の x86 互換プラットフォームでこれが当てはまるという保証はありません)。したがって、スレッドの同期にはこれを使用しないでください。

何らかのフラグが必要な場合は、その目的のためにEventオブジェクトと関数を使用することを検討する必要があります。WaitForSingleObject

于 2009-07-27T04:36:14.043 に答える
0

あなたは大丈夫なはずです。揮発性であるため、オプティマイザがあなたを野蛮にすることはありません。また、32ビット値であるため、少なくともほぼアトミックである必要があります。考えられる驚きの1つは、命令パイプラインがそれを回避できるかどうかです。

一方、保護されたルーチンを使用するための追加コストはいくらですか?

于 2009-04-23T01:50:18.553 に答える
0

現在の値の読み取りには、ロックは必要ありません。

于 2009-04-23T01:51:06.820 に答える
0

Interlocked *関数は、2つの異なるプロセッサが同じメモリにアクセスするのを防ぎます。シングルプロセッサシステムでは、問題はありません。異なるコアにスレッドがあり、両方がこの値にアクセスしているデュアルコアシステムの場合、Interlocked*なしでアトミックと思われることを実行する際に問題が発生する可能性があります。

于 2009-04-23T01:53:14.397 に答える
0

あなたの最初の理解は基本的に正しいです。Windowsがサポートする(またはサポートする予定の)すべてのMPプラットフォームでWindowsが必要とするメモリモデルによると、volatileとマークされた自然に整列された変数からの読み取りは、マシンワードのサイズよりも小さい限りアトミックです。書き込みと同じです。'lock'プレフィックスは必要ありません。

インターロックを使用せずに読み取りを行うと、プロセッサの順序が変更される可能性があります。これは、限られた状況でx86でも発生する可能性があります。変数からの読み取りは、別の変数の書き込みの上に移動される場合があります。Windowsがサポートするほとんどすべての非x86アーキテクチャでは、明示的なインターロックを使用しない場合、さらに複雑な並べ替えが必要になります。

比較交換ループを使用している場合は、交換する変数を揮発性としてマークする必要があるという要件もあります。理由を示すコード例を次に示します。

long g_var = 0;  // not marked 'volatile' -- this is an error

bool foo () {
    long oldValue;
    long newValue;
    long retValue;

    // (1) Capture the original global value
    oldValue = g_var;

    // (2) Compute a new value based on the old value
    newValue = SomeTransformation(oldValue);

    // (3) Store the new value if the global value is equal to old?
    retValue = InterlockedCompareExchange(&g_var,
                                          newValue,
                                          oldValue);

    if (retValue == oldValue) {
        return true;
    }

    return false;
}

うまくいかない可能性があるのは、コンパイラが揮発性でない場合はいつでもg_varからoldValueを再フェッチする権限の範囲内にあることです。この「再実体化」最適化は、レジスターの圧力が高いときにレジスターがスタックにこぼれるのを防ぐことができるため、多くの場合に優れています。

したがって、関数のステップ(3)は次のようになります。

// (3) Incorrectly store new value regardless of whether the global
//     is equal to old.
retValue = InterlockedCompareExchange(&g_var,
                                      newValue,
                                      g_var);
于 2012-05-10T07:46:03.883 に答える
0

読み取りは問題ありません。32 ビット値は、キャッシュ ラインで分割されていない限り、常に全体として読み取られます。アライン 8 は、常にキャッシュ ライン内にあることを保証するので、問題ありません。

命令の並べ替えやナンセンスなことは忘れてください。結果は常に順番にリタイアされます。それ以外の場合は、プロセッサのリコールになります!!!

デュアル CPU マシン (つまり、最も遅い FSB を介して共有) の場合でも、CPU が MESI プロトコルを介してキャッシュの一貫性を保証するので問題ありません。保証されていない唯一のことは、読み取った値が完全に最新ではない可能性があることです。しかし、とにかく最新のものは何ですか?読み取った値に基づいてその場所に書き戻さない場合、ほとんどの状況でこれを知る必要はありません。それ以外の場合は、最初にインターロックされた ops を使用して処理していたでしょう。

要するに、読み取り時に Interlocked ops を使用しても、何も得られません (おそらく、コードを保守する次の人に慎重に対処するように思い出させることを除いて - 繰り返しになりますが、その人は最初からコードを保守する資格がない可能性があります)。

編集: Adrian McCarthyが残したコメントに応えて。

コンパイラの最適化の効果を見落としています。コンパイラは、値が既にレジスタにあると判断した場合、メモリから再読み取りする代わりに、その値を再利用します。また、コンパイラは、目に見える副作用がないと判断した場合、最適化のために命令の並べ替えを行うことがあります。

不揮発性変数からの読み取りが問題ないとは言いませんでした。問題は、インターロックが必要かどうかだけでした。実際、問題の変数は で明確に宣言されていましvolatileた。それとも、キーワードの効果を見落としていましたvolatile?

于 2011-07-07T01:36:43.843 に答える