let mut x = 0;fn foo() { let mut y = &mut x; *y = 1; println!("{}", *y); // foo expects 1}fn bar() { let mut z = &mut x; *z = 2; println!("{}", *z); // bar expects 2}
foo
, one executing bar
./* foo */ let mut y = &mut x;/* foo */ *y = 1;/* foo */ println!("{}", *y); // foo expects 1 // => 1/* bar */ let mut z = &mut x;/* bar */ *z = 2;/* bar */ println!("{}", *z); // bar expects 2 // => 2
foo
and bar
may be interleaved arbitrarily, causing unexpected
results:/* bar */ let mut z = &mut x;/* bar */ *z = 2;/* foo */ let mut y = &mut x;/* foo */ *y = 1;/* bar */ println!("{}", *z); // bar expects 2 // => 1/* foo */ println!("{}", *y); // foo expects 1 // => 1
x
in the previous example.x
in the previous example.i
writes to
buffer[i]
, then tries to read from the entire buffer to decide its next
action.std::thread
.use std::thread;thread::spawn(|| { println!("Hello, world!");});
thread::spawn
returns a thread handler of type JoinHandler
.use std::thread;let handle: JoinHandler = thread::spawn(|| { "Hello, world!"});println!("{:?}", handle.join().unwrap());// => Ok("Hello, world!")
join()
will block the current thread until the handled thread has terminated.join()
returns Ok
of the thread's return value, or an Err
of the thread's panic!
value.Rust threads panic!
independently:
Only the thread that panics will crash.
The thread will unwind its stack, cleaning up resources.
panic!
can be read from other threads.Freeing allocations, running destructors.
std::thread::Thread
JoinHandler
provides .thread()
to get that thread's Thread
.Thread
with thread::current()
.std::thread::Thread
thread::park()
.Thread
s can be unparked with .unpark()
.use std::thread;let handle = thread::spawn(|| { thread::park(); println!("Good morning!");});println!("Good night!");handle.thread().unpark();
use std::thread;for i in 0..10 { thread::spawn(|| { println!("I'm first!"); });}
use std::thread;for i in 0..10 { thread::spawn(|| { println!("I'm #{}!", i); });}// Error!// closure may outlive the current function, but it borrows `i`,// which is owned by the current function
i
.move
to make a movable closure that takes ownership of its scope.use std::thread;for i in 0..10 { thread::spawn(move || { println!("I'm #{}!", i); });}
Same thing as when you return a closure from a function.
Send
and Sync
Send
: a type can be safely transferred between threads.Sync
: a type can be safely shared (with references) between threads.Send
and Sync
are marker traits, which don't implement any methods.Send
pub unsafe trait Send { }
Send
type may have its ownership tranferred across threads.Send
enforces that a type may not leave its original thread.Send
.Send
types are also Send
.Sync
pub unsafe trait Sync { }
Sync
type can be safely shared between threads (i.e. cannot introduce
memory unsafety).Sync
if it is safe to share between threads (&T
is Send
).&T
) and simple inherited mutability (Box<T>
) are
Sync
.&mut T
is actually also Sync
-- because a reference to an &mut T
is
immutable.Sync
types are also Sync
.Send
and Sync
.Cell
and RefCell
: Cells explicitly allow interior mutability with
immutable referencesRc
: reference counting is not synchronizedSend
and Sync
are unsafe
to implement, even though they have no
required functionality.unsafe
indicates that the implementation of the trait
must be trusted to uphold the trait's guarantees.Send
and Sync
are unsafe because thread safety is not a property that can
be guaranteed by Rust's safety checks.Send
and Sync
require a level of trust that safe code alone cannot
provide.Send
is auto-derived for all types whose members are all Sync
.Sync
is auto-derived for all types whose members are all
Send
.impl
ed, since they require no members:unsafe impl Send for Foo {}unsafe impl Sync for Foo {}
Sync
but aren't (due to unsafe
implementation) must be marked explicitly.#![feature(optin_builtin_traits)]impl !Send for Foo {}impl !Sync for Foo {}
The acronym "OIBIT", while quite fun to say, is quite the anachronism. It stands for "opt-in builtin trait". But in fact, Send and Sync are neither opt-in (rather, they are opt-out) nor builtin (rather, they are defined in the standard library). It seems clear that it should be changed. —nikomatsakis
use std::thread;use std::time::Duration;fn main() { let mut data = vec![1, 2, 3]; for i in 0..3 { thread::spawn(move || { data[i] += 1; }); } thread::sleep(Duration::from_millis(50));}// error: capture of moved value: `data`// data[i] += 1;// ^~~~
data
, and then independently
take ownership of data
, data
would have multiple owners!data
, we need some type we can share safely between threads.Sync
.std::sync::Arc<T>
Arc<T>
, an Atomic Reference-Counted pointer!Rc
, but is thread-safe due to atomic reference counting.Weak
variant.use std::sync::Arc;use std::thread;use std::time::Duration;fn main() { let mut data = Arc::new(vec![1, 2, 3]); for i in 0..3 { let data = data.clone(); // Increment `data`'s ref count thread::spawn(move || { data[i] += 1; }); } thread::sleep(Duration::from_millis(50));}
error: cannot borrow immutable borrowed content as mutable data[i] += 1; ^~~~
Rc
, Arc
has no interior mutability.Arc
naturally prohibits doing this.Arc<T>
assumes its contents must be Sync
as well, so we can't mutate
anything inside the Arc
. :(RefCell
; those aren't thread-safe.Mutex<T>
.std::sync::Mutex<T>
fn lock(&self) -> LockResult<MutexGuard<T>>
mutex.lock()
get access to the value inside.lock
blocks until the mutex becomes
unlocked.try_lock
instead.MutexGuard
is a pointer to the data inside the mutex.let mutex = Mutex::new(0);let mut guard = mutex.lock().unwrap();*guard += 1;
lock()
returns a LockResult
.Ok(MutexGuard)
: the mutex was not poisoned and may be used.Err(PoisonError<MutexGuard>)
: a poisoned mutex.into_inner()
, get_ref()
, or get_mut()
on the PoisonError
.use std::sync::Arc;use std::thread;use std::time::Duration;fn main() { let mut data = Arc::new(Mutex::new(vec![1, 2, 3])); for i in 0..3 { let data = data.clone(); // Increment `data`'s ref count thread::spawn(move || { let mut data = data.lock().unwrap(); data[i] += 1; }); } thread::sleep(Duration::from_millis(50));}
std::sync::mpsc
Sender
SyncSender
Receiver
Sender
or SyncSender
can be used to send data to a Receiver
std::sync::mpsc
(Sender<T>, Receiver<T>)
pair may be created using the
channel<T>()
function.Sender
is an asynchronous channel.Sender
has a conceptually-infinite buffer.Receiver
will block the receiving thread until data arrives.std::sync::mpsc
use std::thread;use std::sync::mpsc::channel;fn main() { let (tx, rx) = channel(); for i in 0..10 { let tx = tx.clone(); thread::spawn(move|| { tx.send(i).unwrap(); }); } drop(tx); let mut acc = 0; while let Ok(i) = rx.recv() { acc += i; } assert_eq!(acc, 45);}
std::sync::mpsc
(SyncSender<T>, Receiver<T>)
pair may be created using the
sync_channel<T>()
function.SyncSender
is, naturally, synchronized.SyncSender
does block when you send a message.SyncSender
has a bounded buffer, and will block until there is buffer
space available.Receiver
is the same as the one we got from channel()
, it will
obviously also block when it tries to receive data.std::sync::mpsc
Result
, where an error
indicates the other half of the channel "hung up" (was dropped).Send
, Sync
, and Arc
Sync
Keyboard shortcuts
↑, ←, Pg Up, k | Go to previous slide |
↓, →, Pg Dn, Space, j | Go to next slide |
Home | Go to first slide |
End | Go to last slide |
b / m / f | Toggle blackout / mirrored / fullscreen mode |
c | Clone slideshow |
p | Toggle presenter mode |
t | Restart the presentation timer |
?, h | Toggle this help |
Esc | Back to slideshow |