shared_lock/lib.rs
1//! This crate provides the [`Lock`] and [`Locked`] types which allow one lock to guard
2//! multiple objects.
3//!
4//! # Motivation
5//!
6//! The following pattern is common in languages such as C and Java:
7//!
8//! ```c
9//! struct Context {
10//! mutex_t mutex;
11//! int protected_data1;
12//! };
13//!
14//! struct Child {
15//! struct Context *context;
16//! int protected_data2;
17//! };
18//! ```
19//!
20//! Here, `protected_data1` and `protected_data2` are protected by the same mutex.
21//! Whenever the code wants to access either of those fields, it first locks the mutex.
22//!
23//! Since there is only one mutex, this provides the following properties:
24//!
25//! 1. The code does not need to reason about locking order to avoid deadlocks.
26//! 2. Only one expensive atomic RMW operation has to be performed to access all fields.
27//!
28//! Using common Rust data structures, this might be modeled as follows:
29//!
30//! ```
31//! use std::cell::Cell;
32//! use std::sync::Arc;
33//! use parking_lot::{Mutex, ReentrantMutex};
34//!
35//! struct Context {
36//! mutex: Mutex<()>,
37//! protected_data1: ReentrantMutex<Cell<i32>>,
38//! }
39//!
40//! struct Child {
41//! context: Arc<Context>,
42//! protected_data2: ReentrantMutex<Cell<i32>>,
43//! }
44//! ```
45//!
46//! The code would then acquire the mutex at a very high level and acquire the reentrant
47//! mutexes ad-hoc whenever a field needs to be accessed.
48//!
49//! This achieves the first property of not having to reason about the lock order, but
50//! it does not achieve the second property.
51//!
52//! This crate improves on this by providing data structures that follow the C model
53//! closely.
54//!
55//! # Example
56//!
57//! Using the data structures from this crate, the example above could be implemented as
58//! follows:
59//!
60//! ```
61//! use std::cell::Cell;
62//! use std::sync::Arc;
63//! use shared_lock::{Lock, Locked};
64//!
65//! struct Context {
66//! lock: Lock,
67//! protected_data1: Locked<Cell<i32>>,
68//! }
69//!
70//! struct Child {
71//! context: Arc<Context>,
72//! protected_data2: Locked<Cell<i32>>,
73//! }
74//!
75//! // Constructing the objects:
76//! let lock = Lock::default();
77//! let context = Arc::new(Context {
78//! protected_data1: lock.wrap(Cell::new(0)),
79//! lock,
80//! });
81//! let child = Arc::new(Child {
82//! context: context.clone(),
83//! protected_data2: context.lock.wrap(Cell::new(0)),
84//! });
85//!
86//! // Accessing the objects:
87//! let guard = &context.lock.lock();
88//! context.protected_data1.get(guard).set(1);
89//! child.protected_data2.get(guard).set(1);
90//! ```
91//!
92//! [`Lock`] is a re-entrant lock. If it has not yet been acquired, calling [`Lock::lock`]
93//! is about as expensive as acquiring a regular mutex. If it has already been acquired by
94//! the current thread, calling [`Lock::lock`] is about as expensive as cloning an [`Rc`].
95//!
96//! [`Locked::get`] only performs a single non-atomic comparison before returning a
97//! reference to the contained object, making it effectively free in most situations.
98//!
99//! Since [`Locked::get`] can be called any number of times, it only returns a shared
100//! reference. The contained data must use interior mutability to be mutated. However,
101//! [`Locked`] implements [`Sync`] as long as the contained type implements [`Send`],
102//! meaning that the contained type can use cheap non-atomic interior mutability such as
103//! [`Cell`] and [`RefCell`].
104
105#[cfg(doc)]
106use std::{
107 cell::{Cell, RefCell},
108 rc::Rc,
109};
110pub use {
111 lock::{Guard, Lock},
112 locked::Locked,
113};
114
115mod execution_unit;
116mod lock;
117mod locked;