Skip to content

Latest commit

 

History

History
170 lines (132 loc) · 5.18 KB

Sync-Send.md

File metadata and controls

170 lines (132 loc) · 5.18 KB

Table of contents


URLs

Trait URL
Send std::marker::Send
Sync std::marker::Sync

Send and Sync

From compiler point of view thread is a scope {}.
rustc uses Sync and Send traits to determine is it safe or not to move or share by immutable reference some value to another thread (scope):

  • Sync means that sharing (by immutable reference) between threads is safe. Sync allows an object to to be used by two threads A and B at the same time;
  • Send means that passing (by value) to another thread is safe, in other words type T it can be created in one thread (scope) and dropped in another thread (scope). Send allows an object to be used by two threads A and B at different times:
    • thread A can create and use an object;
    • then object is sent to thread B and thread B can use the object while thread A cannot;

Note:
T is Send if ownership of a value of that type can be transferred to another thread.
T is Sync if and only if &T is Send.


Both Sync and Send are marker traits and they both are unsafe:

pub unsafe auto trait Send { }
pub unsafe auto trait Sync { }

Both Sync and Send are auto traits, which means that they are automatically implemented for your types based on their fields. A struct in which all fields are Send and Sync is also Send and Sync.

The way to opt out of Send or/and Sync is to add a field to your type that does not implement the trait. For that purpouse, there is special std::marker::PhantomData<T> type.
The std::marker::PhantomData<T> is treated by the compiler as a T, except it doesn't actually exist at runtime, in other words, it takes no space at runtime.

Example (disable Sync for type X):

use std::cell::Cell;
use std::marker::PhantomData;

struct X {
    a: i32,
    _not_sync: PhantomData<Cell<()>>
}

fn main() {

}

!Send and !Sync

  • !Send types cannot be moved or copied to other threads, i.e., type is bound to the current thread;
  • !Sync types can only be used by a single thread at any different time, since their references cannot be moved or copied to other threads. But instances of !Sync types can still be moved between threads if they implement Send;

Send + Sync

All primitive types are both Send and Sync:

  • i8, f32, bool, char, &str, ...;
  • (T1, T2), [T; N], &[T], struct { x: T }, ...;
  • String, Option<T>, Vec<T>, Box<T>, ...;
  • AtomicBool, AtomicU8, ...;
  • Arc<T>;
  • Mutex<T>;

Send + !Sync

These types can be moved to other threads, but they’re not thread-safe:

  • Cell;
  • RefCell;
  • UnsafeCell;
  • OnceCell;
  • mpsc::Sender<T>;
  • mpsc::Receiver<T>;

!Send + Sync

These types are thread-safe, but they cannot be moved to another thread:

  • MutexGuard<T: Sync>;

MutexGuard<T: Sync> uses OS kernel API (POSIX Threads, aka pthread) in particular syscalls: pthread_mutex_lock() and pthread_mutex_unlock().
The pthread_mutex_unlock() must be called in the same thread where pthread_mutex_lock() was called.
More details here.


!Send + !Sync

These types are not thread-safe and cannot be moved to other threads:

  • Rc<T>;
  • raw pointers (*const T, *mut T) are neither Send nor Sync, since the compiler doesn't know much about what they represent;
  • types from external libraries that are not thread safe;

Explicit implementation of Send or/and Sync trait for !Sync or/and !Send types requires unsafe keyword, since the compiler cannot check for you if it's correct.
Example:

struct Y {
    a: *mut i32
}

unsafe impl Send for Y {}
unsafe impl Sync for Y {}

fn main() {

}

Rc<T>

If 2 threads attempt to clone Rc that points to the same value, they might try to increment the reference counter at the same time, which is UB, because Rc doesn't use atomic operations.

So, Rc<T> implements !Send and !Sync:

impl<T: ?Sized, A: Allocator> !Send for Rc<T, A> {}
impl<T: ?Sized, A: Allocator> !Sync for Rc<T, A> {}

*mut T

The *mut T implements !Send and !Sync:

impl<T> !Send for *mut T
where
    T: ?Sized,
impl<T> !Sync for *mut T
where
    T: ?Sized,

*const T

The *const T implements !Send and !Sync:

impl<T> !Send for *const T
where
    T: ?Sized,
impl<T> !Sync for *const T
where
    T: ?Sized,