Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

First 3 sections of implementing a mutex #409

Open
wants to merge 4 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
47 changes: 47 additions & 0 deletions src/arc-mutex/mutex-adding--RAII--suport.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
# Adding RAII Support
While the version we made in the last chapter is usable, it requires unsafe code on the part of the user to unlock it and doesn't the user from using the mutable reference after the lock has been freed. We can solve these problems by using a RAII guard.

The guard itself will use the `core::ops::Deref` trait to allow it to still be used as if it were a mutable reference and the `Drop` trait to ensure that the mutex is unlocked when it leaves scope.

First we implement `Deref` and `DerefMut`:

use core::ops::{Deref,MutDeref};

struct MutexGuard<'a,T>(&'a Mutex<T>);

impl<'a,T> Deref for MutexGuard<'a,T>{
type Target = T;
fn deref(&self)->&T{
unsafe{&*self.0.value.get()}
}
}

impl<'a,T> DerefMut for MutexGuard<'a,T>{


Then add a `Drop` implementation, to unlock the mutex when it leaves scope:

impl<'a,T> Drop for MutexGuard<'a,T>{
fn drop(&mut self){
unsafe{self.0.unlock()}
}
}

Finally, we need to change our `lock()` method to return a mutex guard instead of a raw mutable reference (this should replace the existing lock method on mutex):

impl<T> Mutex<T>{
pub fn lock(&self)->MutexGuard<T>{
while self.is_locked.compare_exchange(
false,//expecting it to not be locked
true ,//lock it if it is not already locked
Ordering::Acquire,//If we succeed, use acquire ordering to prevent
Ordering::Release//If we faill to acquire the lock, it does not matter which ordering we choose, so choose the fastest.
).is_err(){}//If we fail to aquire the lock immediately try again.

fence(Ordering::Acquire);//prevent writes to the data protected by the mutex
//from being reordered before the mutex is actually acquired.
return MutexGuard(self);
}
}

> Written with [StackEdit](https://stackedit.io/).
31 changes: 31 additions & 0 deletions src/arc-mutex/mutex-layout-and-boiler-plate.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
# Layout & Boilerplate
First we should make a layout for our mutex. The minimum would be a Boolean to store if the mutex is locked or not and an Unsafe Cell to store the actual value inside the mutex. For now, we can use the following layout:

use core::cell::UnsafeCell;
use core::sync::atomic::{AtomicBool,Ordering};

struct Mutex<T>{
is_locked:AtomicBool,
value:UnsafeCell,
}

We also should fill out some boilerplate:

impl<T> Mutex<T>{
pub fn new(value:T)->Self{
Self{is_locked:AtomicBool::new(false),UnsafeCell::new(value)}
}

pub fn get_mut(&mut self)->&mut T{
self.value.get_mut()
}

pub fn into_inner(self)->T{
self.value.into_inner()
}
}
And a sync impl:

impl<T:Send+Sync> Sync for Mutex<T>{}

> Written with [StackEdit](https://stackedit.io/).
40 changes: 40 additions & 0 deletions src/arc-mutex/mutex-locking-and-unlocking.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@


# Locking and Unlocking
Locking and unlocking is simple with our layout; Make sure the mutex is in the correct state, then toggle it.

use core::sync::fence;

impl<T> Mutex<T>{
pub fn lock(&self)->MutexGuard<T>{
while self.is_locked.compare_exchange(
false,//expecting it to not be locked
true ,//lock it if it is not already locked
Ordering::Acquire,//If we succeed, use acquire ordering to prevent
Ordering::Release//If we faill to acquire the lock, it does not matter which ordering we choose, so choose the fastest.
).is_err(){}//If we fail to aquire the lock immediately try again.

fence(Ordering::Acquire);//prevent writes to the data protected by the mutex from being reordered before the mutex is actually acquired.
//safety:The previous steps we took means that no one else has the lock
unsafe{return &mut self.value.get()}
}

///Safety:Make sure the caller has acquired the lock, and will not dereference the return mutable reference again.
pub unsafe fn unlock(&self){
if self.is_locked.compare_exchange(
true,//The caller should ensure the mutex is locked
false,//Mark the lock as unlocked
Ordering::Release,
Ordering::Relaxed,//We are about to panic anyway, the ordering doesn't matter
).is_err{
panic!("Unlock called while the lock was not locked")
};

fence(Ordering::Acquire);//prevent writes to the data protected by the mutex
//from being reordered before the mutex is actually acquired.
}
}



> Written with [StackEdit](https://stackedit.io/).