2016-09-13 15 views
-1

新しい値を計算するためにベクトルの不変なリストを使用しているこの単純な例を考えてみましょう。 calculate_vecを想定し読み取り専用のデータを複製せずにマルチスレッド関数呼び出しを実行するには?

use std::collections::LinkedList; 

fn calculate_vec(v: &Vec<i32>) -> i32 { 
    let mut result: i32 = 0; 
    for i in v { 
     result += *i; 
    } 
    return result; 
} 

fn calculate_from_list(list: &LinkedList<Vec<i32>>) -> LinkedList<i32> { 
    let mut result: LinkedList<i32> = LinkedList::new(); 
    for v in list { 
     result.push_back(calculate_vec(v)); 
    } 
    return result; 
} 

fn main() { 
    let mut list: LinkedList<Vec<i32>> = LinkedList::new(); 
    // some arbitrary values 
    list.push_back(vec![0, -2, 3]); 
    list.push_back(vec![3, -4, 3]); 
    list.push_back(vec![7, -10, 6]); 

    let result = calculate_from_list(&list); 
    println!("Here's the result!"); 
    for i in result { 
     println!("{}", i); 
    } 
} 

は、プロセッサ集約関数であり、我々はこれを実行するために、複数のスレッドを使用したい場合があり、次の例は動作しますが、が必要です(私はと思われるもの:この作業、シングルスレッドの例を考えると

は不要なベクタークローンです。

use std::collections::LinkedList; 

fn calculate_vec(v: &Vec<i32>) -> i32 { 
    let mut result: i32 = 0; 
    for i in v { 
     result += *i; 
    } 
    return result; 
} 

fn calculate_from_list(list: &LinkedList<Vec<i32>>) -> LinkedList<i32> { 
    use std::thread; 
    let mut result: LinkedList<i32> = LinkedList::new(); 
    let mut join_handles = LinkedList::new(); 
    for v in list { 
     let v_clone = v.clone(); // <-- how to avoid this clone? 
     join_handles.push_back(thread::spawn(move || calculate_vec(&v_clone))); 
    } 
    for j in join_handles { 
     result.push_back(j.join().unwrap()); 
    } 
    return result; 
} 

fn main() { 
    let mut list: LinkedList<Vec<i32>> = LinkedList::new(); 
    // some arbitrary values 
    list.push_back(vec![0, -2, 3]); 
    list.push_back(vec![3, -4, 3]); 
    list.push_back(vec![7, -10, 6]); 

    let result = calculate_from_list(&list); 
    println!("Here's the result!"); 
    for i in result { 
     println!("{}", i); 
    } 
} 

この例では動作しますが、ベクトルは不変であるので、それは、ベクターが複製されている場合にのみ、 は、しかし、論理的に、私は、これが必要する必要があるとは思いません。

calculate_vecを呼び出すたびに新しいベクターを割り当てる必要はありません。

この単純な例は、クロージャに渡される前にデータを複製する必要なしに、どのようにマルチスレッド化できますか?


更新、それは所有権を取得する必要がないにもかかわらず、@のKERの提案に基づいてArcを使用していますHERESにworking example


注1)私はスレッドを処理するために、サードパーティのライブラリがある知っていますが、これは錆の標準ライブラリを使用して可能であるかどうかを知るために興味があります。

注2)スレッディングに関する同様の質問がありますが、例ではスレッドにデータを書き込むことがよくありますが、ここではそうではありません。

+0

わからない、これは重複し、その確かに関連しているが、この質問を具体的に寿命に関係していません - ちょうど読み取り専用データの複製を避ける方法について説明します。 – ideasman42

+0

クローニングしていないコードではそのエラーが発生しているはずですので、私はそれが重複していると思います。なぜなら、他の質問に対する回答は基本的にはこの質問と同じですからです。同じ回答については異なる質問をするのは大丈夫ですが、質問は私の意見では重複しています。私はしかし、stackoverflowのルールを誤解しているかもしれません。 –

+0

ここで重要なのは、データが読み取り専用であり、データの複製が望ましくないコストを招く可能性があるということです。たとえば答えのポイントの1つは、データをコピーすることを示唆しています。私はエラーを避ける方法を尋ねていません、私は 'クローン'を避ける方法を尋ねています。 – ideasman42

答えて

1

問題を解決する方法は複数あります。

  1. ベクターをArc<LinkedList<Vec<i32>>>に移動してクローンします。計算後、try_unwrapを使用してLinkedList<Vec<i32>>を返すことができます。これはRust標準ライブラリだけで動作します。

    を使用するHeres a working exampleですが、LinkedListは、インデックス作成を可能にするためにVecに置き換えられました。
    この場合、関数はこの関数に渡される引数を所有する必要があることにも注意してください。

  2. crossbeamクレートを使用してスコープを参照できるスレッドを作成し、手ですべてjoin_handlesコードを実行する必要がなくなります。これは、あなたが望むのとまったく同じように動作するため、コードに与える影響は最小限に抑えられます。

    crossbeam::scope(|scope| { 
        for v in list { 
         scope.spawn(|| calculate_vec(&v)) 
        } 
    }); 
    
  3. scoped_threadpoolクレートを使用してください。 crossbeamのように動作しますが、タスクごとに1つのスレッドを作成するのではなく、限られた数のスレッドでタスクを分散します。(感謝@delnan)

  4. は、直接データの並列処理のためのrayonクレートを使用

    use rayon::prelude::*; 
    list.par_iter().map(|v| calculate_vec(&v)).collect() 
    
+1

良い答え! Rustを学ぶ人にとっては、最初に 'クローン' OPが取り除こうとしている理由について少し情報を提供することが役立つかもしれません。言い換えれば、Rustのどのルールには、あなたが提示する解決策やクローンが必要ですか? –

+0

一般的には素晴らしいアイデアですが、この正確な質問に対する答えは範囲外です。クローンが除外されている場合は、すでに複数の質問と回答が参照とスレッドに関連しています。しかし、確かにその推論を質問に加えることは良いことです。 –

+1

RE:#2 - 'crossbeam'はすべての' v'に対してgodスレッドに対して正直なものを起動します。大規模なデータセットの場合、通常は 'scoped_threadpool'のようなスレッドプールを使う方が良いでしょう。 – delnan

関連する問題