2017-02-20 16 views
2

大きな配列(3e9要素)のデータがあり、その値を複数のスレッドで更新しています。私は競合状態があることを知りました。データの一部をロックとして使用できますか?

要素が互いに独立しているので、関数全体をロックする必要はないと思いますが、とdata[234]の更新は同時に安全に行うことができます。

また、data[]の各要素の最上位ビットが決して使用されないことがわかりました。そのビットにGCC原子組み込みロックを実装するのは安全ですか?

私のコードは次のとおりですが、デッドロックが発生しているようです。

const unsigned short LOCK_MASK = 1<<15; 
unsigned short * lock = &(data[position]); 
unsigned short oldLock, newLock; 

//lock 
do { 
    oldLock = *lock; 
    newLock = oldLock^LOCK_MASK; 
} while ((oldLock & LOCK_MASK) || !__sync_bool_compare_and_swap(lock, oldLock, newLock)); 

//update data[position] here 
... 
... 
... 

//unlock 
*lock ^= LOCK_MASK; 

私も(Lightweight spinlocks built from GCC atomic operations?)この記事を読んで、私の設計では、私のdata

EDITvolatileを追加し、0のロックが解除意味し、1つの手段は、あなたのコードは番号が含まれて

+0

ロックを取得する前にすべての読み取りで同期が必要です。特に、 'oldLock = * lock;'は間違っています、それは原子的である必要があります。そのままでは、オプティマイザは、 '(oldLock&LOCK_MASK)'が一度真であった場合、 '* lock'が決して変更されないと想定するかもしれません。 –

+1

より慎重なアプローチは、適切な数の実際のミューテックス(またはスピンロックまたは使用するメカニズム)を割り当て、それらを使用してデータへのアクセスを同期させることです。例えばM個のミューテックスの配列を持っている場合は、データ配列に値#iを読み書きする前に、各スレッドがmutex#(i%M)をロックするようにしてください。ミューテックスの競合が受け入れられるほど稀である最小の値を見つけるまで、Mの値を調整します。 –

答えて

1

をロックしていますoldLock = *lockを含むビットのロックを解除し、*lock ^= LOCK_MASK, などのデータ競合が発生し、リリースバリアが存在しないために他のコアとの同期が失敗します。

書き込みアクセスのためにアレイセグメントをロックするだけでなく、読み取りと書き込みを同期させる必要があるため、そのセグメントを読み取りアクセス用にロックする必要があることにも注意してください。

GCC原子組み込みロックをそのビットに実装するのは安全ですか?あなたが読み取りのための別々の状態を表現し、(ロック解除、読み取りロックのx N、書き込みロック)アクセスを書きたい場合は

複数のビットが必要とされています。

const unsigned short LOCK_MASK = 1<<15; 

void lock_array_segment(int position) 
{ 
    unsigned short *lock = &data[position]; // global array 
    unsigned short oldLock, newLock; 

    do { 
     oldLock = __atomic_load_n (lock, __ATOMIC_RELAXED); 
     newLock = oldLock | LOCK_MASK; // set bit 

    } while ((oldLock & LOCK_MASK) || !__sync_bool_compare_and_swap(lock, oldLock, newLock)); 
} 


void unlock_array_segment(int position) 
{ 
    unsigned short *lock = &data[position]; // global array 
    unsigned short oldLock, newLock; 

    oldLock = __atomic_load_n (lock, __ATOMIC_RELAXED); 
    newLock = oldLock & ~LOCK_MASK; // clear bit 

    __atomic_store_n (lock, newLock, __ATOMIC_RELEASE); 
} 

__sync_bool_compare_and_swapのドキュメントは、ほとんどの場合には言い、これらの組み込みコマンドは次のとおりです。2つの状態にロック 単一ビットの制限は、はあなたのコードに基づいて、で実装することができ、を、アンロックロック完全な障壁と考えられた。ここでは障壁を獲得する必要があるので、それをカバーする必要があります。

アプローチはスピンロックに基づいているため、読み取りロックを長時間保持したい場合はうまく機能しません。その場合、ロックを必要とするデータ配列内の各セグメントに対して別々のミューテックスを使用するより簡単なアプローチを検討してください。 複数のリーダーにアクセスを許可する場合は、std::shared_mutex(C++ 17)またはboost::shared_mutexを使用することを検討してください。

+0

は以前は__atomic_ *命令を知りませんでした!ありがとう – bbvan

0

標準的なロック方法(C++11以上)を検討する必要があります。

(少なくともそこに説明されている概念については)Pthread tutorialを読むことから始めてください。

C++でatomic operationsthread supportについて読む。

連続1024(または他のいくつかの2のべき乗)要素の各セグメントにmutexを持つことをヒューリスティックに考えることができます。

producer-consumerと考えてよいでしょう。

関連する問題