How do I use a Condvar to limit multithreading?

霸气de小男生 提交于 2020-01-14 09:38:19

问题


I'm trying to use a Condvar to limit the number of threads that are active at any given time. I'm having a hard time finding good examples on how to use Condvar. So far I have:

use std::sync::{Arc, Condvar, Mutex};
use std::thread;

fn main() {
    let thread_count_arc = Arc::new((Mutex::new(0), Condvar::new()));
    let mut i = 0;
    while i < 100 {
        let thread_count = thread_count_arc.clone();
        thread::spawn(move || {
            let &(ref num, ref cvar) = &*thread_count;
            {
                let mut start = num.lock().unwrap();
                if *start >= 20 {
                    cvar.wait(start);
                }
                *start += 1;
            }
            println!("hello");
            cvar.notify_one();
        });
        i += 1;
    }
}

The compiler error given is:

error[E0382]: use of moved value: `start`
  --> src/main.rs:16:18
   |
14 |                     cvar.wait(start);
   |                               ----- value moved here
15 |                 }
16 |                 *start += 1;
   |                  ^^^^^ value used here after move
   |
   = note: move occurs because `start` has type `std::sync::MutexGuard<'_, i32>`, which does not implement the `Copy` trait

I'm entirely unsure if my use of Condvar is correct. I tried staying as close as I could to the example on the Rust API. Wwat is the proper way to implement this?


回答1:


Here's a version that compiles:

use std::sync::{Arc, Condvar, Mutex};
use std::thread;

fn main() {
    let thread_count_arc = Arc::new((Mutex::new(0u8), Condvar::new()));
    let mut i = 0;
    while i < 100 {
        let thread_count = thread_count_arc.clone();
        thread::spawn(move || {
            let &(ref num, ref cvar) = &*thread_count;
            {
                let mut start = num.lock().unwrap();
                while *start >= 20 {
                    start = cvar.wait(start).unwrap()
                }
                *start += 1;
            }
            println!("hello");
            cvar.notify_one();
        });
        i += 1;
    }
}

The important part can be seen from the wait API:

pub fn wait<'a, T>(
    &self, 
    guard: MutexGuard<'a, T>
) -> LockResult<MutexGuard<'a, T>>

This says that wait consumes the guard, which is why you get the error you did - you no longer own start, so you can't call any methods on it!

This function is actually doing a great job of reflecting how Condvars work - you give up the lock on the Mutex (represented by start) for a while, and when the function returns you get the lock again.

The fix is to give up the lock and then grab the lock guard return value from wait. I've also switched from an if to a while, as encouraged by huon.




回答2:


For reference, the usual way to have a limited number of threads in a given scope is with a Semaphore.

Unfortunately, Semaphore was never stabilized, was deprecated in Rust 1.8 and was removed in Rust 1.9. There are crates available that add semaphores on top of other concurrency primitives.

let sema = Arc::new(Semaphore::new(20)); 

for i in 0..100 {
    let sema = sema.clone();
    thread::spawn(move || {
        let _guard = sema.acquire();
        println!("{}", i);
    })
}

This isn't quite doing the same thing: since each thread is not printing the total number of the threads inside the scope when that thread entered it.




回答3:


I realized the code I provided didn't do exactly what I wanted it to, so I'm putting this edit of Shepmaster's code here for future reference.

use std::sync::{Arc, Condvar, Mutex};
use std::thread;

fn main() {
    let thread_count_arc = Arc::new((Mutex::new(0u8), Condvar::new()));
    let mut i = 0;
    while i < 150 {
        let thread_count = thread_count_arc.clone();
        thread::spawn(move || {
            let x;
            let &(ref num, ref cvar) = &*thread_count;
            {
                let start = num.lock().unwrap();
                let mut start = if *start >= 20 {
                    cvar.wait(start).unwrap()
                } else {
                    start
                };
                *start += 1;
                x = *start;
            }
            println!("{}", x);
            {
                let mut counter = num.lock().unwrap();
                *counter -= 1;
            }
            cvar.notify_one();
        });
        i += 1;
    }
    println!("done");
}

Running this in the playground should show more or less expected behavior.



来源:https://stackoverflow.com/questions/29870837/how-do-i-use-a-condvar-to-limit-multithreading

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!