use std::{thread::scope, net::TcpStream, io::Read};
fn main(){
let mut a = TcpStream::connect("[::1]:1024").unwrap();
let mut b = TcpStream::connect("[::1]:1025").unwrap();
let mut v = vec![a];
scope(|s|{
s.spawn(||unsafe{v.push(b)});
s.spawn(||{
let mut r = [0u8; 8];
unsafe{v[0].read(&mut r);}
});
})
}
The push doesn't change the 1st element, even unsafe block is denied when compiling. Is it possible to do those jobs simultaneously?
This is not true. push may (actually in this case it will!) reallocate, invalidating the previous storage, included the first element. Consider this shenario:
thread 2 acquires a reference to v[0]
thread 1 pushes, reallocating. The reference to v[0] becomes invalid
thread 2 actually uses the reference to v[0] to call read, but is now invalid and thus you get UB.
unsafe code doesn't let you directly bypass the borrow checker. It only allows you to (source):
Dereference a raw pointer
Call an unsafe function or method
Access or modify a mutable static variable
Implement an unsafe trait
Access fields of unions
You'll have to preallocate the memory for b to avoid any reallocations in v. Note that this is not fully general: depending on what you may want to actually do this could be unfeasible. For example Rust Playground
One correct solution to write to and read from a Vec concurrently is to use Arc and Mutex. For example:
use std::thread::scope;
use std::sync::{Arc, Mutex};
fn main(){
let v = vec![10];
let v1 = Arc::new(Mutex::new(v));
let v2 = v1.clone();
scope(|s|{
s.spawn(|| v1.lock().unwrap().push(20));
s.spawn(|| {
let x = v2.lock().unwrap()[0];
println!("Got: {x:?}");
});
})
}
Yes, thanks for pointing that out. I just noticed myself. I haven't used scoped threads yet.
I added a final assertion to demonstrate that the two threads are really executed:
use std::thread::scope;
use std::sync::Mutex;
fn main(){
let v = vec![10];
let m = Mutex::new(v);
scope(|s|{
s.spawn(|| m.lock().unwrap().push(20));
s.spawn(|| {
let x = m.lock().unwrap()[0];
println!("Got: {x:?}");
});
});
let v = m.into_inner().unwrap();
assert_eq!(v, vec![10, 20]);
}
If you use std::thread::spawn, you also need the Arc. The following example compiles on Playground:
use std::{thread::spawn, net::TcpStream, io::Read};
use std::sync::{Arc, Mutex};
fn main() {
let a = TcpStream::connect("[::1]:1024").unwrap();
let b = TcpStream::connect("[::1]:1025").unwrap();
let v = vec![a];
let v1 = Arc::new(Mutex::new(v));
let v2 = v1.clone();
let t1 = spawn(move || v1.lock().unwrap().push(b));
let t2 = spawn(move || {
let mut r = [0u8; 8];
v2.lock().unwrap()[0].read(&mut r).unwrap();
});
t1.join().unwrap();
t2.join().unwrap();
}
Yes, the way an Arc lets you share stuff is via the .clone() method. Cloning an Arc gives you a new "handle" to the same shared value — the value inside it is not cloned. Now, any single clone of the Arc can only be in one thread, so the way you share them is by first making a clone, and then giving the clone to the new thread. (You can't call clone inside the new task! It has to happen before you spawn.)