├── .gitignore ├── Cargo.toml ├── README.md ├── benches └── bench.rs ├── src └── lib.rs └── tests └── tests.rs /.gitignore: -------------------------------------------------------------------------------- 1 | 2 | /target 3 | **/*.rs.bk 4 | Cargo.lock -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "try-mutex" 3 | version = "0.4.2" 4 | edition = "2018" 5 | description = "Fast non-blocking mutex" 6 | categories = ["concurrency", "no-std"] 7 | authors = ["Mike Pedersen "] 8 | license = "MIT" 9 | readme = "README.md" 10 | repository = "https://github.com/mpdn/try-mutex" 11 | 12 | [[bench]] 13 | name = "bench" 14 | harness = false 15 | 16 | [dev-dependencies] 17 | criterion = "0.3.4" 18 | static_assertions = "1.1.0" 19 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | A simple non-blocking mutex (i.e. only `try_lock` is supported), using atomics. 2 | 3 | Simpler than the one found in stdlib. Does not support poisoning. 4 | 5 | This used to be faster than the mutex in the standard library, but benchmarking indicates that 6 | optimizations in the standard library means there is no longer a significant difference 7 | (on my machine). Be sure to run them on your own machine to compare. 8 | 9 | Nevertheless, this library may still be useful for embedded or similar cases. 10 | -------------------------------------------------------------------------------- /benches/bench.rs: -------------------------------------------------------------------------------- 1 | #![allow(clippy::mutex_atomic)] 2 | 3 | use criterion::{black_box, Criterion, criterion_group, criterion_main}; 4 | use std::sync::Mutex; 5 | use try_mutex::TryMutex; 6 | 7 | fn build_try() { 8 | for _ in 0..100000 { 9 | black_box(TryMutex::new(false)); 10 | } 11 | } 12 | 13 | fn build_std() { 14 | for _ in 0..100000 { 15 | black_box(Mutex::new(false)); 16 | } 17 | } 18 | 19 | fn lock_try() { 20 | let m = TryMutex::new(false); 21 | for _ in 0..100000 { 22 | let mut g = m.try_lock().unwrap(); 23 | *g = !*g; 24 | } 25 | } 26 | 27 | fn lock_std() { 28 | let m = Mutex::new(false); 29 | for _ in 0..100000 { 30 | let mut g = m.try_lock().unwrap(); 31 | *g = !*g; 32 | } 33 | } 34 | 35 | fn contested_try() { 36 | let m = TryMutex::new(false); 37 | for _ in 0..100000 { 38 | let mut g = m.try_lock().unwrap(); 39 | black_box(m.try_lock()); 40 | *g = !*g; 41 | } 42 | } 43 | 44 | fn contested_std() { 45 | let m = Mutex::new(false); 46 | for _ in 0..100000 { 47 | let mut g = m.try_lock().unwrap(); 48 | std::mem::drop(black_box(m.try_lock())); 49 | *g = !*g; 50 | } 51 | } 52 | 53 | fn criterion_benchmark(c: &mut Criterion) { 54 | c.bench_function("build_try", |b| b.iter(build_try)); 55 | c.bench_function("build_std", |b| b.iter(build_std)); 56 | c.bench_function("lock_try", |b| b.iter(lock_try)); 57 | c.bench_function("lock_std", |b| b.iter(lock_std)); 58 | c.bench_function("contested_try", |b| b.iter(contested_try)); 59 | c.bench_function("contested_std", |b| b.iter(contested_std)); 60 | } 61 | 62 | criterion_group!(benches, criterion_benchmark); 63 | criterion_main!(benches); 64 | -------------------------------------------------------------------------------- /src/lib.rs: -------------------------------------------------------------------------------- 1 | //! Provides a simple mutex that does not support blocking or poisoning, but is 2 | //! faster and simpler than the mutex in stdlib. 3 | 4 | #![no_std] 5 | 6 | use core::cell::UnsafeCell; 7 | use core::convert::From; 8 | use core::fmt; 9 | use core::fmt::{Debug, Display}; 10 | use core::marker::PhantomData; 11 | use core::ops::{Deref, DerefMut}; 12 | use core::panic::{RefUnwindSafe, UnwindSafe}; 13 | use core::sync::atomic::{AtomicBool, Ordering}; 14 | 15 | /// A mutual exclusion primitive that does not support blocking or poisoning. 16 | /// This results in a simpler and faster implementation. 17 | pub struct TryMutex { 18 | data: UnsafeCell, 19 | locked: AtomicBool, 20 | } 21 | 22 | impl TryMutex { 23 | /// Create a new mutex in unlocked state. 24 | #[inline] 25 | pub const fn new(t: T) -> Self { 26 | TryMutex { 27 | data: UnsafeCell::new(t), 28 | locked: AtomicBool::new(false), 29 | } 30 | } 31 | 32 | /// Attemps to acquire a lock on this mutex. If this mutex is currently 33 | /// locked, `None` is returned. Otherwise a RAII guard is returned. The lock 34 | /// will be unlocked when the guard is dropped. 35 | #[inline] 36 | pub fn try_lock(&self) -> Option> { 37 | self.locked 38 | .compare_exchange(false, true, Ordering::Acquire, Ordering::Relaxed) 39 | .ok() 40 | .map(|_| TryMutexGuard { 41 | lock: self, 42 | notsend: PhantomData, 43 | }) 44 | } 45 | 46 | /// Consumes this mutex, returning the underlying data. 47 | #[inline] 48 | pub fn into_inner(self) -> T { 49 | self.data.into_inner() 50 | } 51 | 52 | /// Retrieve a mutable reference to the underlying data. Since this mutably 53 | /// borrows the mutex, no actual locking needs to take place. 54 | #[inline] 55 | pub fn get_mut(&mut self) -> &mut T { 56 | unsafe { &mut *self.data.get() } 57 | } 58 | } 59 | 60 | impl Default for TryMutex { 61 | fn default() -> Self { 62 | Self::new(T::default()) 63 | } 64 | } 65 | 66 | impl Debug for TryMutex { 67 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { 68 | if let Some(guard) = self.try_lock() { 69 | f.debug_struct("TryMutex").field("data", &*guard).finish() 70 | } else { 71 | struct LockedPlaceholder; 72 | impl fmt::Debug for LockedPlaceholder { 73 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { 74 | f.write_str("") 75 | } 76 | } 77 | 78 | f.debug_struct("TryMutex") 79 | .field("data", &LockedPlaceholder) 80 | .finish() 81 | } 82 | } 83 | } 84 | 85 | /// A RAII scoped lock on a `TryMutex`. When this this structure is dropped, the 86 | /// mutex will be unlocked. 87 | pub struct TryMutexGuard<'a, T: 'a> { 88 | lock: &'a TryMutex, 89 | notsend: PhantomData<*mut T>, 90 | } 91 | 92 | impl<'a, T> Deref for TryMutexGuard<'a, T> { 93 | type Target = T; 94 | 95 | fn deref(&self) -> &T { 96 | unsafe { &*self.lock.data.get() } 97 | } 98 | } 99 | 100 | impl<'a, T> DerefMut for TryMutexGuard<'a, T> { 101 | fn deref_mut(&mut self) -> &mut T { 102 | unsafe { &mut *self.lock.data.get() } 103 | } 104 | } 105 | 106 | impl<'a, T> Drop for TryMutexGuard<'a, T> { 107 | fn drop(&mut self) { 108 | self.lock.locked.store(false, Ordering::Release); 109 | } 110 | } 111 | 112 | impl<'a, T: Debug> Debug for TryMutexGuard<'a, T> { 113 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { 114 | f.debug_struct("TryMutexGuard") 115 | .field("data", &**self) 116 | .finish() 117 | } 118 | } 119 | 120 | impl<'a, T: Display> Display for TryMutexGuard<'a, T> { 121 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { 122 | (**self).fmt(f) 123 | } 124 | } 125 | 126 | impl UnwindSafe for TryMutex {} 127 | impl RefUnwindSafe for TryMutex {} 128 | unsafe impl Send for TryMutex {} 129 | unsafe impl Sync for TryMutex {} 130 | unsafe impl<'a, T: Sync> Sync for TryMutexGuard<'a, T> {} 131 | unsafe impl<'a, T: Send> Send for TryMutexGuard<'a, T> {} 132 | 133 | impl From for TryMutex { 134 | fn from(t: T) -> Self { 135 | TryMutex::new(t) 136 | } 137 | } 138 | -------------------------------------------------------------------------------- /tests/tests.rs: -------------------------------------------------------------------------------- 1 | use std::sync::Arc; 2 | use std::thread; 3 | use try_mutex::{TryMutex, TryMutexGuard}; 4 | use static_assertions::assert_impl_all; 5 | 6 | #[test] 7 | fn get_mut() { 8 | let mut m = TryMutex::new(0); 9 | *m.get_mut() += 1; 10 | assert!(m.into_inner() == 1); 11 | } 12 | 13 | #[test] 14 | fn single_thread() { 15 | let m = TryMutex::new(0); 16 | *m.try_lock().unwrap() += 1; 17 | assert!(m.into_inner() == 1); 18 | } 19 | 20 | #[test] 21 | fn across_threads() { 22 | let a = Arc::new(TryMutex::new(false)); 23 | let b = a.clone(); 24 | thread::spawn(move || { 25 | *a.try_lock().unwrap() = true; 26 | }) 27 | .join() 28 | .unwrap(); 29 | assert!(*b.try_lock().unwrap()); 30 | } 31 | 32 | #[test] 33 | fn only_one_lock() { 34 | let m = TryMutex::new(false); 35 | 36 | { 37 | let mut a = m.try_lock().unwrap(); 38 | assert!(m.try_lock().is_none()); 39 | *a = true; 40 | } 41 | 42 | assert!(*m.try_lock().unwrap()) 43 | } 44 | 45 | #[test] 46 | fn debug_print() { 47 | println!("{:?}", TryMutex::new(false)); 48 | } 49 | 50 | 51 | #[test] 52 | fn debug_print_guard() { 53 | let m = TryMutex::new(false); 54 | println!("{:?}", m.try_lock().unwrap()); 55 | } 56 | 57 | assert_impl_all!(TryMutex: Sync); 58 | assert_impl_all!(TryMutexGuard<'static, bool>: Sync); 59 | assert_impl_all!(TryMutexGuard<'static, bool>: Send); 60 | --------------------------------------------------------------------------------