24
Rust - Synchronization and Concurrency Safe synchronization abstractions and their implementation Corey (Rust) Richardson February 17, 2014

Rust Synchronization Primitives

Embed Size (px)

DESCRIPTION

These are slides for a presentation I gave to my OS class. It was mostly me talking, using the code to drive the examples and show how Rust approaches the problems of synchronization. Not super detailed, doesn't get into the meaty stuff (Condvar, mutex::Mutex, etc)

Citation preview

Page 1: Rust Synchronization Primitives

Rust - Synchronization and ConcurrencySafe synchronization abstractions and their implementation

Corey (Rust) Richardson

February 17, 2014

Page 2: Rust Synchronization Primitives

What is Rust?

Rust is a systems language, aimed at replacing C++, with thefollowing design goals, in roughly descending order of importance:

I Zero-cost abstraction

I Easy, safe concurrency and parallelism

I Memory safety (no data races)

I Type safety (no willy-nilly casting)

I Simplicity

I Compilation speed

Page 3: Rust Synchronization Primitives

Concurrency model

I ”Tasks” as unit of computation

I No observable shared memory

I No race conditions! †

Page 4: Rust Synchronization Primitives

No race conditions?

I How can we avoid race conditions?

I A type system which enables safe sharing of data

I Careful design of concurrency abstractions

Page 5: Rust Synchronization Primitives

Hello World

fn main() {

println("Hello, world!");

}

Page 6: Rust Synchronization Primitives

UnsafeArc

Unsafe data structure. Provides atomic reference counting of atype. Ensures memory does not leak.

pub struct UnsafeArc<T> {

priv data: *mut ArcData<T>

}

struct ArcData<T> {

count: AtomicUint,

data: T

}

Page 7: Rust Synchronization Primitives

UnsafeArc cont.

fn new_inner<T>(data: T, initial_count: uint) -> *mut ArcData<T> {

let data = box ArcData {

count: AtomicUint::new(initial_count),

data: data

}

cast::transmute(data)

}

impl<T: Send> UnsafeArc<T> {

pub fn new(data: T) -> UnsafeArc<T> {

unsafe { UnsafeArc { data: new_inner(data, 1) } }

}

pub fn new2(data: T) -> (UnsafeArc<T>, UnsafeArc<T>) {

unsafe {

let ptr = new_inner(data, 2);

(UnsafeArc { data: ptr }, UnsafeArc { data: ptr })

}

}

Page 8: Rust Synchronization Primitives

UnsafeArc cont.pub fn get(&self) -> *mut T {

unsafe {

// problems?

assert!((*self.data).count.load(Relaxed) > 0);

return &mut (*self.data).data as *mut T;

}

}

pub fn get_immut(&self) -> *T {

unsafe {

// problems?

assert!((*self.data).count.load(Relaxed) > 0);

return &(*self.data).data as *T;

}

}

pub fn is_owned(&self) -> bool {

unsafe {

// problems?

(*self.data).count.load(Relaxed) == 1

}

}

}

Page 9: Rust Synchronization Primitives

UnsafeArc cloning

impl<T: Send> Clone for UnsafeArc<T> {

fn clone(&self) -> UnsafeArc<T> {

unsafe {

let old_count =

(*self.data).count

.fetch_add(1, Acquire);

// ^~~~~~~ Why?

assert!(old_count >= 1);

return UnsafeArc { data: self.data };

}

}

}

Page 10: Rust Synchronization Primitives

Adding Safety

Arc: wraps UnsafeArc, provides read-only access.

pub struct Arc<T> { priv x: UnsafeArc<T> }

impl<T: Freeze + Send> Arc<T> {

pub fn new(data: T) -> Arc<T> {

Arc { x: UnsafeArc::new(data) }

}

pub fn get<’a>(&’a self) -> &’a T {

unsafe { &*self.x.get_immut() }

}

}

Page 11: Rust Synchronization Primitives

Mutexes?

pub struct Mutex { priv sem: Sem<~[WaitQueue]> }

impl Mutex {

pub fn new() -> Mutex { Mutex::new_with_condvars(1) }

pub fn new_with_condvars(num: uint) -> Mutex {

Mutex { sem: Sem::new_and_signal(1, num) }

}

pub fn lock<U>(&self, blk: || -> U) -> U {

// magic?

(&self.sem).access(blk)

}

pub fn lock_cond<U>(&self,

blk: |c: &Condvar| -> U) -> U {

(&self.sem).access_cond(blk)

}

}

Page 12: Rust Synchronization Primitives

Mutexes!

Mutexes in Rust are implemented on top of semaphores, using 100No ‘unlock’ operation? Closures!

Page 13: Rust Synchronization Primitives

Wait Queues

Wait queues provide an ordering when waiting on a lock.

// Each waiting task receives on one of these.

type WaitEnd = Port<()>;

type SignalEnd = Chan<()>;

// A doubly-ended queue of waiting tasks.

struct WaitQueue {

head: Port<SignalEnd>,

tail: Chan<SignalEnd>

}

Page 14: Rust Synchronization Primitives

Channels and Ports

Message passing. Provides a way to send ‘Send‘ data to anothertask. Very efficient, single-reader, single-writer.

impl <T: Send> Chan<T> {

fn send(&self, data: T) { ... }

fn try_send(&self, data: T) -> bool { ... }

}

impl <T: Send> Port<T> {

fn recv(&self) -> T { ... }

fn try_recv(&self) -> TryRecvResult<T> { ... }

}

Page 15: Rust Synchronization Primitives

Wait Queue ImplementationGiven Ports and Chans, how can we express wait queues?

impl WaitQueue {

fn signal(&self) -> bool {

match self.head.try_recv() {

comm::Data(ch) => {

// Send a wakeup signal. If the waiter

// was killed, its port will

// have closed. Keep trying until we

// get a live task.

if ch.try_send(()) {

true

} else {

self.signal()

}

}

_ => false

}

}

Page 16: Rust Synchronization Primitives

Wait Queue Impl Cont.

fn broadcast(&self) -> uint {

let mut count = 0;

loop {

match self.head.try_recv() {

comm::Data(ch) => {

if ch.try_send(()) {

count += 1;

}

}

_ => break

}

}

count

}

Page 17: Rust Synchronization Primitives

Wait Queue Impl End

fn wait_end(&self) -> WaitEnd {

let (wait_end, signal_end) = Chan::new();

assert!(self.tail.try_send(signal_end));

wait_end

}

}

Page 18: Rust Synchronization Primitives

Raw Semaphores

We have a way to express order and waiting, now to build someactual *synchronization*.

struct Sem<Q>(UnsafeArc<SemInner<Q>>);

struct SemInner<Q> {

lock: LowLevelMutex,

count: int,

waiters: WaitQueue,

// Can be either unit or another waitqueue.

// Some sems shouldn’t come with

// a condition variable attached, others should.

blocked: Q

}

Page 19: Rust Synchronization Primitives

Semaphore Implementation

impl<Q: Send> Sem<Q> {

pub fn access<U>(&self, blk: || -> U) -> U {

(|| {

self.acquire();

blk()

}).finally(|| {

self.release();

})

}

unsafe fn with(&self, f: |&mut SemInner<Q>|) {

let Sem(ref arc) = *self;

let state = arc.get();

let _g = (*state).lock.lock();

// unlock????

f(cast::transmute(state));

}

Page 20: Rust Synchronization Primitives

Acquiring a semaphore (P)

pub fn acquire(&self) {

unsafe {

let mut waiter_nobe = None;

self.with(|state| {

state.count -= 1;

if state.count < 0 {

// Create waiter nobe, enqueue ourself, and tell

// outer scope we need to block.

waiter_nobe = Some(state.waiters.wait_end());

}

});

// Need to wait outside the exclusive.

if waiter_nobe.is_some() {

let _ = waiter_nobe.unwrap().recv();

}

}

}

Page 21: Rust Synchronization Primitives

Releasing a Semaphore (V)

pub fn release(&self) {

unsafe {

self.with(|state| {

state.count += 1;

if state.count <= 0 {

state.waiters.signal();

}

})

}

}

}

Page 22: Rust Synchronization Primitives

Filling in the last pieces

impl Sem<~[WaitQueue]> {

fn new_and_signal(count: int, num_condvars: uint) -> Sem<~[WaitQueue]> {

let mut queues = ~[];

for _ in range(0, num_condvars) { queues.push(WaitQueue::new()); }

Sem::new(count, queues)

}

}

Page 23: Rust Synchronization Primitives

And more?

On top of these primitives, as we have seen in class, every othersynchronization primitive can be constructed. In particular, we alsoprovide starvation-free Reader-Writer locks, Barriers, andCopy-on-Write Arcs.

Page 24: Rust Synchronization Primitives

Thank You

Thanks for your time!