Multithreading CPU绑定的工作线程的协作中断习惯用法是什么?

Multithreading CPU绑定的工作线程的协作中断习惯用法是什么?,multithreading,rust,Multithreading,Rust,我正在Rust中试验多线程。我已经创建了一个玩具的例子,基于埃拉托什尼筛寻找素数。每个工作线程都有一个素数列表,作为新候选线程的除数进行检查 for chunk in &primes { let tx2 = mpsc::Sender::clone(&tx); let p2 = Arc::clone(chunk); thread::spawn(move || { let result = divisible_by_any(i, &p2

我正在Rust中试验多线程。我已经创建了一个玩具的例子,基于埃拉托什尼筛寻找素数。每个工作线程都有一个素数列表,作为新候选线程的除数进行检查

for chunk in &primes {
    let tx2 = mpsc::Sender::clone(&tx);
    let p2 = Arc::clone(chunk);
    thread::spawn(move || {
        let result = divisible_by_any(i, &p2.lock().unwrap());
        tx2.send(result).unwrap();
    });
}
let mut any = false;
for _i in 0..primes.len() {
    let result = rx.recv().unwrap();
    if result { any = true }
}
一种可能的优化是,一旦任何线程找到除数,就允许领头进程“中断”工作线程。我设想工作线程(
可被任何除数除数)不会被杀死,但会检查某种信号量,以指示是否已请求“中断”,从而停止对除数的扫描


Rust的习惯用法是什么,用于提升多个CPU绑定的工作线程可以注意到/采样的信号量?

Rust中的文字中断通常不会执行,因为中断的副作用会干扰涉及借用和删除的语言保证。如果您不同意让工作线程将精力浪费在剩余的块上,那么这里的最佳实践是让工作线程定期轮询某种简单的状态对象

let quit_flag = Arc::new(AtomicBool::new(false));
for chunk in &primes {
    let tx2 = mpsc::Sender::clone(&tx);
    let p2 = Arc::clone(chunk);
    let qf = Arc::clone(quit_flag);
    thread::spawn(move || {
        // repeatedly accessing an &AtomicBool is cheaper than an 
        // Arc<AtomicBool>, so we're unwrapping it here.
        let quit = qf.as_ref(); 
        // divisible_by_any needs to check the quit flag occasionally.
        let result = divisible_by_any(i, &p2.lock().unwrap(), quit);
        tx2.send(result).unwrap();
    });
}
for _i in 0..primes.len() {
    let result = rx.recv().unwrap();
    if result.is_some() { (*quit_flag).store(true, Ordering::Relaxed); }
}

// Some parts of this are best-guesses and pseudocode
fn divisible_by_any(i: Integer, chunk: Chunk, quit: &AtomicBool) -> Option<Integer>
{
    for sub_chunk in chunk.sub_chunks() {
        if quit.load(Ordering::Relaxed) {
            return None;
        }
        for j in sub_chunk {
            // do work, maybe returning Some(Integer)
        }
    }
    None
}
let quit_flag=Arc::new(AtomicBool::new(false));
用于组块输入和素数{
让tx2=mpsc::Sender::clone(&tx);
设p2=Arc::clone(chunk);
设qf=Arc::clone(退出_标志);
线程::生成(移动| |{
//反复访问&AtomicBool比
//弧,所以我们在这里展开它。
让quit=qf.as_ref();
//可除的_by_任何人都需要偶尔检查退出标志。
让结果=可被任意除的(i,&p2.lock().unwrap(),quit);
tx2.send(result).unwrap();
});
}
对于0..primes.len()中的_i{
让结果=rx.recv().unwrap();
if result.is_some(){(*quit_flag).store(true,Ordering::released);}
}
//其中有些部分是最佳猜测和伪代码
fn可被任意(i:Integer,chunk:chunk,quit:&AtomicBool)->Option
{
对于chunk中的sub_chunk.sub_chunks(){
如果退出。加载(排序::放松){
不返回任何值;
}
对于sub_块中的j{
//执行工作,可能返回一些(整数)
}
}
没有一个
}

Rust中的文字中断通常不会执行,因为中断的副作用可能会干扰涉及借用和删除的语言保证。如果您不同意让工作线程将精力浪费在剩余的块上,那么这里的最佳实践是让工作线程定期轮询某种简单的状态对象

let quit_flag = Arc::new(AtomicBool::new(false));
for chunk in &primes {
    let tx2 = mpsc::Sender::clone(&tx);
    let p2 = Arc::clone(chunk);
    let qf = Arc::clone(quit_flag);
    thread::spawn(move || {
        // repeatedly accessing an &AtomicBool is cheaper than an 
        // Arc<AtomicBool>, so we're unwrapping it here.
        let quit = qf.as_ref(); 
        // divisible_by_any needs to check the quit flag occasionally.
        let result = divisible_by_any(i, &p2.lock().unwrap(), quit);
        tx2.send(result).unwrap();
    });
}
for _i in 0..primes.len() {
    let result = rx.recv().unwrap();
    if result.is_some() { (*quit_flag).store(true, Ordering::Relaxed); }
}

// Some parts of this are best-guesses and pseudocode
fn divisible_by_any(i: Integer, chunk: Chunk, quit: &AtomicBool) -> Option<Integer>
{
    for sub_chunk in chunk.sub_chunks() {
        if quit.load(Ordering::Relaxed) {
            return None;
        }
        for j in sub_chunk {
            // do work, maybe returning Some(Integer)
        }
    }
    None
}
let quit_flag=Arc::new(AtomicBool::new(false));
用于组块输入和素数{
让tx2=mpsc::Sender::clone(&tx);
设p2=Arc::clone(chunk);
设qf=Arc::clone(退出_标志);
线程::生成(移动| |{
//反复访问&AtomicBool比
//弧,所以我们在这里展开它。
让quit=qf.as_ref();
//可除的_by_任何人都需要偶尔检查退出标志。
让结果=可被任意除的(i,&p2.lock().unwrap(),quit);
tx2.send(result).unwrap();
});
}
对于0..primes.len()中的_i{
让结果=rx.recv().unwrap();
if result.is_some(){(*quit_flag).store(true,Ordering::released);}
}
//其中有些部分是最佳猜测和伪代码
fn可被任意(i:Integer,chunk:chunk,quit:&AtomicBool)->Option
{
对于chunk中的sub_chunk.sub_chunks(){
如果退出。加载(排序::放松){
不返回任何值;
}
对于sub_块中的j{
//执行工作,可能返回一些(整数)
}
}
没有一个
}

为了澄清,工作线程应该停止还是等待?为了澄清,工作线程应该停止还是等待?我尝试了这个习惯用法,它似乎有效。不幸的是,对于我的玩具示例来说,实例化
Arc
所带来的性能损失完全抵消了通过提前停止线程可能获得的任何性能增益。这不会降低答案的正确性或在更复杂的应用程序中的实用性。我刚刚意识到,我忘记了退出.store(true,Ordering::Relaxed)
。也许您的示例会从添加该代码中受益?更新答案后,我应该删除此评论。
quit\u标志
设置在主线程中,就在
rx.recv()
行之后。我的方法应该可以工作,但在工作线程中设置quit标志确实可以减少总延迟。不幸的是,对于我的玩具示例来说,实例化
Arc
所带来的性能损失完全抵消了通过提前停止线程可能获得的任何性能增益。这不会降低答案的正确性或在更复杂的应用程序中的实用性。我刚刚意识到,我忘记了退出.store(true,Ordering::Relaxed)
。也许您的示例会从添加该代码中受益?更新答案后,我应该删除此评论。
quit\u标志
设置在主线程中,就在
rx.recv()
行之后。我的方法应该可以工作,但在工作线程中设置quit标志确实会减少总体延迟。