2016-09-26 5 views
0

私はRustのC++チェスエンジンを移植しています。私は検索スレッド間で共有される大きなハッシュテーブルを持っており、このテーブルはC++バージョンではロックレスです。読み取り/書き込みアクセスを共有するためのmutexはありません。ご興味のある方はthe theoryです。Rustのスレッド間でロックレスリソースを共有する

このコードの錆のバージョンでは、それが正常に動作しているが、Mutexを使用しています。

let shared_hash = Arc::new(Mutex::new(new_hash())); 

for _ in 0..n_cpu { 
    println!("start thread"); 
    let my_hash = shared_hash.clone(); 
    thread_pool.push(thread::spawn(move || { 
     let mut my_hash = my_hash.lock().unwrap(); 
     let mut search_engine = SearchEngine::new(); 
     search_engine.search(&mut myhash); 
    })); 
} 

for i in thread_pool { 
    let _ = i.join(); 
} 

どのように私はミューテックスなしでスレッド間のテーブルを共有するだろうか?

+2

これに適切な答えがあるかどうかはわかりません。データ構造がロックを必要としない場合は、ミューテックスは必要ありません。もしそうなら、あなたはミューテックスを必要とし、その使用を避けようとするのは危険です。 「ミューテックスなし」以外で何をしようとしているのかを指定していないため、既存の適用可能なソリューションがあるかどうかはわかりません。もしそうでなければ、これは "あまりにも広いために閉じられるかもしれない" "ライブラリを推薦する"か、 "どのようにロックフリーのデータ構造を書くのか"(これも*おそらく広すぎる) 。 –

+0

また、そのリンクをめくってみると(記事全体を読んで少し漠然とした質問に答える時間がない)、なぜあなたは 'Cell 'を使うことができないのだろうと思います。しかし、私は 'new_hash()'が何をしているのか分からないので、\ * shrug \ * –

+2

Matthieu M.が答えているように、データ構造がスレッド間で安全に共有できることを示す 'Sync'を実装する必要があります。これは問題をあなたの本物の質問に変えます:「私はこの** **ロックレスなデータ構造をRustでどうやって書くのですか?」そのためには、安全でないコードを記述する必要があります。問題のハッシュテーブルには、**ハードウェア**の前提条件(たとえば、メモリへの64ビットストアは自然なアトミック)と**ソフトウェア**前提条件(たとえば、2つの64ビット値の保存)が必要です。これはデータ構造が移植性がないことを示しているようです。 – Shepmaster

答えて

4

実際には、基本的な構造が既にSyncの場合はMutexは不要です。

あなたの場合、例えばアトミックの構造体の配列が動作します。 Rustの利用可能なアトミックはhereです。示唆したように

-1

、私はSyncを実装していますが、実際にはハッシュテーブルをロックしないFakeMutexを書いた:

use core::cell::UnsafeCell; 
use core::marker::Sync; 
use core::ops::{Deref, DerefMut}; 

pub struct FakeMutex<T> { 
    data: UnsafeCell<T>, 
} 

pub struct FakeMutexGuard<'a, T: 'a> { 
    data: &'a mut T, 
} 

unsafe impl<T> Sync for FakeMutex<T> {} 

impl<T> FakeMutex<T> { 
    pub fn new(user_data: T) -> FakeMutex<T> { 
     FakeMutex { 
      data: UnsafeCell::new(user_data), 
     } 
    } 

    pub fn lock(&self) -> FakeMutexGuard<T> { 
     FakeMutexGuard { 
      data: unsafe { &mut *self.data.get() }, 
     } 
    } 
} 

impl<'a, T> Deref for FakeMutexGuard<'a, T>{ 
    type Target = T; 
    fn deref<'b>(&'b self) -> &'b T { &*self.data } 
} 

impl<'a, T> DerefMut for FakeMutexGuard<'a, T>{ 
    fn deref_mut<'b>(&'b mut self) -> &'b mut T { &mut *self.data } 
} 

私の新しいコードは次のとおりです。

let shared_hash = Arc::new(FakeMutex::new(new_hash())); 

for _ in 0..n_cpu { 
    println!("start thread"); 
    let my_hash = shared_hash.clone(); 
    thread_pool.push(thread::spawn(move || { 
     let mut my_hash = my_hash.lock(); 
     let mut search_engine = SearchEngine::new(); 
     search_engine.search(&mut myhash); 
    })); 
} 

for i in thread_pool { 
    let _ = i.join(); 
} 

これが私の質問を解決します。

+0

これは良い解決策だとは思わない。具体的には、** Sync **の組み合わせを作るために**他の型をラップするために使用できる型を作成しましたが、あなたの 'FakeMutex' *は実際にそれを安全にしません*! Matthieu M.によると、**基底構造**は既に 'Sync' *です。ハッシュテーブルに対して 'Sync'を実装しなければなりません。どのタイプも魔法のように' Sync'になることを激しく許さないでください。 – Shepmaster

+0

私は無作為の人々がそれにつまずくことを望んでいないので、この回答をdownvotingしています。それは非常に危険であり、マナーの最も微妙なので、このパターンをコピーすることをお勧めします。 – Shepmaster

0

Data races are undefined behavior(C++とRustの両方)。ちょうどいいえ。

正しい方法は、テーブルをatomic integersから作り出すことです。ロケット科学です。 You have to decide case by caseあなたはどれくらいcare about the order of memory operationsです。これによりコードが乱雑になります:

// non-atomic array access 
table[h] = 0; 

// atomic array access 
table[h].store(0, Ordering::SeqCst); 

しかし、それは価値があります。

パフォーマンス上のペナルティがどのようになるかはわかりません。試してみるだけです。

関連する問題