How can I pass a reference to a stack variable to a thread?

后端 未结 1 1067
粉色の甜心
粉色の甜心 2020-11-21 11:37

I\'m writing a WebSocket server where a web client connects to play chess against a multithreaded computer AI. The WebSocket server wants to pass a Logger objec

1条回答
  •  北恋
    北恋 (楼主)
    2020-11-21 11:56

    The thread support in the standard library allows the created threads to outlive the thread that created them; that's a good thing! However, if you were to pass a reference to a stack-allocated variable to one of these threads, there's no guarantee that the variable will still be valid by the time the thread executes. In other languages, this would allow the thread to access invalid memory, creating a pile of memory safety issues.

    Fortunately, we aren't limited to the standard library. At least two crates provide scoped threads — threads that are guaranteed to exit before a certain scope ends. These can ensure that stack variables will be available for the entire duration of the thread:

    • crossbeam
    • scoped-threadpool

    There are also crates that abstract away the low-level details of "threads" but allow you to accomplish your goals:

    • rayon

    Here are examples of each. Each example spawns a number of threads and mutates a local vector in place with no locking, no Arc, and no cloning. Note that the mutation has a sleep call to help verify that the calls are happening in parallel.

    You can extend the examples to share a reference to any type which implements Sync, such as a Mutex or an Atomic*. Using these would introduce locking, however.

    scoped-threadpool

    use scoped_threadpool::Pool; // 0.1.9
    use std::{thread, time::Duration};
    
    fn main() {
        let mut vec = vec![1, 2, 3, 4, 5];
        let mut pool = Pool::new(vec.len() as u32);
    
        pool.scoped(|scoped| {
            for e in &mut vec {
                scoped.execute(move || {
                    thread::sleep(Duration::from_secs(1));
                    *e += 1;
                });
            }
        });
    
        println!("{:?}", vec);
    }
    

    crossbeam

    use crossbeam; // 0.6.0
    use std::{thread, time::Duration};
    
    fn main() {
        let mut vec = vec![1, 2, 3, 4, 5];
    
        crossbeam::scope(|scope| {
            for e in &mut vec {
                scope.spawn(move |_| {
                    thread::sleep(Duration::from_secs(1));
                    *e += 1;
                });
            }
        })
        .expect("A child thread panicked");
    
        println!("{:?}", vec);
    }
    

    rayon

    use rayon::iter::{IntoParallelRefMutIterator, ParallelIterator}; // 1.0.3
    use std::{thread, time::Duration};
    
    fn main() {
        let mut vec = vec![1, 2, 3, 4, 5];
    
        vec.par_iter_mut().for_each(|e| {
            thread::sleep(Duration::from_secs(1));
            *e += 1;
        });
    
        println!("{:?}", vec);
    }
    

    the client is required to box the connection in an Arc when it is internal to the library that the code is parallelized

    Perhaps you can hide your parallelism better then? Could you accept the logger and then wrap it in an Arc / Mutex before handing it off to your threads?

    0 讨论(0)
提交回复
热议问题