7

私たちはかなり古いバージョンのboostを使用しています。アップグレードするまでは、にC++でのアトミックなCAS操作が必要です。 (私たちはどちらかまだC++ 0xのを使用していない)C++で比較してスワップする

私は、次のCAS機能作成:

void myFunc(uint32_t &masterDeserialize) 
{ 
    std::ostringstream debugStream; 

    unsigned int tid = pthread_self(); 
    debugStream << "myFunc, threadId: " << tid << " masterDeserialize= " << masterDeserialize << " masterAddress = " << &masterDeserialize << std::endl; 

    // memory fence 
    __asm__ __volatile__ ("" ::: "memory"); 
    uint32_t retMaster = CAS(&masterDeserialize, 1, 0); 
    debugStream << "After cas, threadid = " << tid << " retMaster = " << retMaster << " MasterDeserialize = " << masterDeserialize << " masterAddress = " << &masterDeserialize << std::endl; 
    if(retMaster != 0) // not master deserializer. 
    { 
     debugStream << "getConfigurationRowField, threadId: " << tid << " NOT master. retMaster = " << retMaster << std::endl; 

     DO SOMETHING... 
    } 
    else 
    { 
     debugStream << "getConfigurationRowField, threadId: " << tid << " MASTER. retMaster = " << retMaster << std::endl; 

     DO SOME LOGIC 

     // Signal we're done deserializing. 
     masterDeserialize = 0; 
    } 
    std::cout << debugStream.str(); 
} 

マイ:機能を使用しています私のコードは次のように多少です

inline uint32_t CAS(volatile uint32_t *mem, uint32_t with, uint32_t cmp) 
{ 
    uint32_t prev = cmp; 
    // This version by Mans Rullgard of Pathscale 
    __asm__ __volatile__ ("lock\n\t" 
      "cmpxchg %2,%0" 
      : "+m"(*mem), "+a"(prev) 
       : "r"(with) 
       : "cc"); 

    return prev; 
} 

をこのコードをテストすると10個のスレッドが生成され、と同じmasterDeserialize変数を使用して関数を呼び出すようにすべてのスレッドに通知します。

これはほとんどの場合よく機能しますが、数千〜数百万回のテスト反復で2つのスレッドが両方ともマスターロックを取得するパスに入ることがあります。

私はこれがどうやって可能か、それを避ける方法はわかりません。

私はmasterDeserializeをリセットする前にメモリフェンスを使用しようとしましたが、CPU OOOが影響を及ぼす可能性があると考えましたが、これは結果に影響しません。

これは明らかに多くのコアを持つマシン上で実行され、デバッグモードでコンパイルされるため、GCCは最適化のために実行順序を変更しないでください。

上記に何が間違っていますか?

EDIT: アセンブリコードの代わりにgccプリミティブを使用しましたが、同じ結果が得られました。

inline uint32_t CAS(volatile uint32_t *mem, uint32_t with, uint32_t cmp) 
{ 
    return __sync_val_compare_and_swap(mem, cmp, with); 
} 
私はマルチコア、マルチCPUマシン上で実行していますが、それは、仮想マシンであり、それはこの動作はVMによって何とか引き起こされている可能性があり

+0

実際に両方が同じコードパスに同時に入っていることをどのように知っていますか?あなたのデバッグ出力はロックによって保護されません。また、完全性のために、弱い順序のアーキテクチャでしか問題になりません(これはx86/x86-64とよく似ています): 'masterDeserialize'の設定はアトミックな操作でなければなりません。 – JustSid

+0

1.コードパス:この関数を呼び出すすべてのスレッドがロードされ、それらのスレッド間で共有されるいくつかのフラグを待ちます。私は制御スレッドからそのフラグをtrueに設定しました。 2.これはx86-64アーキテクチャです。このプログラム用に32ビットとしてコンパイルします。 3. masterDeserializeをアトミックに設定するだけですが、masterDeserialize == trueのコードパスに1つのスレッドしか見つからないように設定するだけで十分ではありません。また、古い値をテストして各スレッドが取るべき道を知っている。 –

+0

また、あるスレッドがmasterDeserialize = trueを取り込んだら、その反復の他のスレッドはそのようにするべきではありません。 (コードセクションはいくつかの逆シリアル化を行い、ポインタがnullでない場合にポインタがnullにならないように設定します) もう1つ言及したいことは、デバッグ情報がそのような奇妙な振る舞いによって示されるコード。 –

答えて

1

このコードでは、2つだけでなく、理論上は任意の数のスレッドを「マスター」にすることができます。問題は、完了後にマスタパスを取ったスレッドがmasterDeserialize変数を0に戻すようにして、(例えばプリエンプションのために)CASに非常に遅れて到着する可能性のあるスレッドによって再度「取得」できるようにすることです。

修正は実際には簡単です。このフラグに3番目の状態(たとえば値2)を追加して「マスターが完了しました」を意味し、この状態を(0の初期状態ではなく)そのジョブを通知するマスターのパスが実行されます。したがって、myFuncを呼び出すスレッドの1つだけが0を見ることができ、必要な保証が得られます。フラグを再利用するには、0に明示的に再初期化する必要があります。

+0

ありがとうアレクセイ!私はこれを逃したために本当に愚かな気がします。 –

関連する問題