crossbeam_skiplist/lib.rs
1//! Concurrent maps and sets based on [skip lists].
2//!
3//! This crate provides the types [`SkipMap`] and [`SkipSet`].
4//! These data structures provide an interface similar to [`BTreeMap`] and [`BTreeSet`],
5//! respectively, except they support safe concurrent access across
6//! multiple threads.
7//!
8//! # Concurrent access
9//! [`SkipMap`] and [`SkipSet`] implement [`Send`] and [`Sync`],
10//! so they can be shared across threads with ease.
11//!
12//! Methods which mutate the map, such as [`insert`],
13//! take `&self` rather than `&mut self`. This allows
14//! them to be invoked concurrently.
15//!
16//! ```
17//! use crossbeam_skiplist::SkipMap;
18//! use crossbeam_utils::thread::scope;
19//!
20//! let person_ages = SkipMap::new();
21//!
22//! scope(|s| {
23//! // Insert entries into the map from multiple threads.
24//! s.spawn(|_| {
25//! person_ages.insert("Spike Garrett", 22);
26//! person_ages.insert("Stan Hancock", 47);
27//! person_ages.insert("Rea Bryan", 234);
28//!
29//! assert_eq!(person_ages.get("Spike Garrett").unwrap().value(), &22);
30//! });
31//! s.spawn(|_| {
32//! person_ages.insert("Bryon Conroy", 65);
33//! person_ages.insert("Lauren Reilly", 2);
34//! });
35//! }).unwrap();
36//!
37//! assert!(person_ages.contains_key("Spike Garrett"));
38//! person_ages.remove("Rea Bryan");
39//! assert!(!person_ages.contains_key("Rea Bryan"));
40//!
41//! ```
42//!
43//! Concurrent access to skip lists is lock-free and sound.
44//! Threads won't get blocked waiting for other threads to finish operating
45//! on the map.
46//!
47//! Be warned that, because of this lock-freedom, it's easy to introduce
48//! race conditions into your code. For example:
49//! ```no_run
50//! use crossbeam_skiplist::SkipSet;
51//! use crossbeam_utils::thread::scope;
52//!
53//! let numbers = SkipSet::new();
54//! scope(|s| {
55//! // Spawn a thread which will remove 5 from the set.
56//! s.spawn(|_| {
57//! numbers.remove(&5);
58//! });
59//!
60//! // While the thread above is running, insert a value into the set.
61//! numbers.insert(5);
62//!
63//! // This check can fail!
64//! // The other thread may remove the value
65//! // before we perform this check.
66//! assert!(numbers.contains(&5));
67//! }).unwrap();
68//! ```
69//!
70//! In effect, a _single_ operation on the map, such as [`insert`],
71//! operates atomically: race conditions are impossible. However,
72//! concurrent calls to functions can become interleaved across
73//! threads, introducing non-determinism.
74//!
75//! To avoid this sort of race condition, never assume that a collection's
76//! state will remain the same across multiple lines of code. For instance,
77//! in the example above, the problem arises from the assumption that
78//! the map won't be mutated between the calls to `insert` and `contains`.
79//! In sequential code, this would be correct. But when multiple
80//! threads are introduced, more care is needed.
81//!
82//! Note that race conditions do not violate Rust's memory safety rules.
83//! A race between multiple threads can never cause memory errors or
84//! segfaults. A race condition is a _logic error_ in its entirety.
85//!
86//! # Mutable access to elements
87//! [`SkipMap`] and [`SkipSet`] provide no way to retrieve a mutable reference
88//! to a value. Since access methods can be called concurrently, providing
89//! e.g. a `get_mut` function could cause data races.
90//!
91//! A solution to the above is to have the implementation wrap
92//! each value in a lock. However, this has some repercussions:
93//! * The map would no longer be lock-free, inhibiting scalability
94//! and allowing for deadlocks.
95//! * If a user of the map doesn't need mutable access, then they pay
96//! the price of locks without actually needing them.
97//!
98//! Instead, the approach taken by this crate gives more control to the user.
99//! If mutable access is needed, then you can use interior mutability,
100//! such as [`RwLock`]: `SkipMap<Key, RwLock<Value>>`.
101//!
102//! # Garbage collection
103//! A problem faced by many concurrent data structures
104//! is choosing when to free unused memory. Care must be
105//! taken to prevent use-after-frees and double-frees, both
106//! of which cause undefined behavior.
107//!
108//! Consider the following sequence of events operating on a [`SkipMap`]:
109//! * Thread A calls [`get`] and holds a reference to a value in the map.
110//! * Thread B removes that key from the map.
111//! * Thread A now attempts to access the value.
112//!
113//! What happens here? If the map implementation frees the memory
114//! belonging to a value when it is
115//! removed, then a user-after-free occurs, resulting in memory corruption.
116//!
117//! To solve the above, this crate uses the _epoch-based memory reclamation_ mechanism
118//! implemented in [`crossbeam-epoch`]. Simplified, a value removed from the map
119//! is not freed until after all references to it have been dropped. This mechanism
120//! is similar to the garbage collection found in some languages, such as Java, except
121//! it operates solely on the values inside the map.
122//!
123//! This garbage collection scheme functions automatically; users don't have to worry about it.
124//! However, keep in mind that holding [`Entry`] handles to entries in the map will prevent
125//! that memory from being freed until at least after the handles are dropped.
126//!
127//! # Performance versus B-trees
128//! In general, when you need concurrent writes
129//! to an ordered collection, skip lists are a reasonable choice.
130//! However, they can be substantially slower than B-trees
131//! in some scenarios.
132//!
133//! The main benefit of a skip list over a `RwLock<BTreeMap>`
134//! is that it allows concurrent writes to progress without
135//! mutual exclusion. However, when the frequency
136//! of writes is low, this benefit isn't as useful.
137//! In these cases, a shared [`BTreeMap`] may be a faster option.
138//!
139//! These guidelines should be taken with a grain of salt—performance
140//! in practice varies depending on your use case.
141//! In the end, the best way to choose between [`BTreeMap`] and [`SkipMap`]
142//! is to benchmark them in your own application.
143//!
144//! # Alternatives
145//! This crate implements _ordered_ maps and sets, akin to [`BTreeMap`] and [`BTreeSet`].
146//! In many situations, however, a defined order on elements is not required. For these
147//! purposes, unordered maps will suffice. In addition, unordered maps
148//! often have better performance characteristics than their ordered alternatives.
149//!
150//! Crossbeam [does not currently provide a concurrent unordered map](https://github.com/crossbeam-rs/rfcs/issues/32).
151//! That said, here are some other crates which may suit you:
152//! * [`DashMap`](https://docs.rs/dashmap) implements a novel concurrent hash map
153//! with good performance characteristics.
154//! * [`flurry`](https://docs.rs/flurry) is a Rust port of Java's `ConcurrentHashMap`.
155//!
156//! [`insert`]: SkipMap::insert
157//! [`get`]: SkipMap::get
158//! [`Entry`]: map::Entry
159//! [skip lists]: https://en.wikipedia.org/wiki/Skip_list
160//! [`crossbeam-epoch`]: https://docs.rs/crossbeam-epoch
161//! [`BTreeMap`]: std::collections::BTreeMap
162//! [`BTreeSet`]: std::collections::BTreeSet
163//! [`RwLock`]: std::sync::RwLock
164//!
165//! # Examples
166//! [`SkipMap`] basic usage:
167//! ```
168//! use crossbeam_skiplist::SkipMap;
169//!
170//! // Note that the variable doesn't have to be mutable:
171//! // SkipMap methods take &self to support concurrent access.
172//! let movie_reviews = SkipMap::new();
173//!
174//! // Insert some key-value pairs.
175//! movie_reviews.insert("Office Space", "Deals with real issues in the workplace.");
176//! movie_reviews.insert("Pulp Fiction", "Masterpiece.");
177//! movie_reviews.insert("The Godfather", "Very enjoyable.");
178//! movie_reviews.insert("The Blues Brothers", "Eye lyked it a lot.");
179//!
180//! // Get the value associated with a key.
181//! // get() returns an Entry, which gives
182//! // references to the key and value.
183//! let pulp_fiction = movie_reviews.get("Pulp Fiction").unwrap();
184//! assert_eq!(*pulp_fiction.key(), "Pulp Fiction");
185//! assert_eq!(*pulp_fiction.value(), "Masterpiece.");
186//!
187//! // Remove a key-value pair.
188//! movie_reviews.remove("The Blues Brothers");
189//! assert!(movie_reviews.get("The Blues Brothers").is_none());
190//!
191//! // Iterate over the reviews. Since SkipMap
192//! // is an ordered map, the iterator will yield
193//! // keys in lexicographical order.
194//! for entry in &movie_reviews {
195//! let movie = entry.key();
196//! let review = entry.value();
197//! println!("{}: \"{}\"", movie, review);
198//! }
199//! ```
200//!
201//! [`SkipSet`] basic usage:
202//! ```
203//! use crossbeam_skiplist::SkipSet;
204//!
205//! let books = SkipSet::new();
206//!
207//! // Add some books to the set.
208//! books.insert("A Dance With Dragons");
209//! books.insert("To Kill a Mockingbird");
210//! books.insert("The Odyssey");
211//! books.insert("The Great Gatsby");
212//!
213//! // Check for a specific one.
214//! if !books.contains("The Winds of Winter") {
215//! println!("We have {} books, but The Winds of Winter ain't one.",
216//! books.len());
217//! }
218//!
219//! // Remove a book from the set.
220//! books.remove("To Kill a Mockingbird");
221//! assert!(!books.contains("To Kill a Mockingbird"));
222//!
223//! // Iterate over the books in the set.
224//! // Values are returned in lexicographical order.
225//! for entry in &books {
226//! let book = entry.value();
227//! println!("{}", book);
228//! }
229//! ```
230
231#![doc(test(
232 no_crate_inject,
233 attr(
234 deny(warnings, rust_2018_idioms, single_use_lifetimes),
235 allow(dead_code, unused_assignments, unused_variables)
236 )
237))]
238#![warn(missing_docs, unsafe_op_in_unsafe_fn)]
239#![cfg_attr(not(feature = "std"), no_std)]
240
241#[cfg(all(feature = "alloc", target_has_atomic = "ptr"))]
242extern crate alloc;
243
244#[cfg(all(feature = "alloc", target_has_atomic = "ptr"))]
245pub mod base;
246#[cfg(all(feature = "alloc", target_has_atomic = "ptr"))]
247#[doc(inline)]
248pub use crate::base::SkipList;
249
250#[cfg(feature = "std")]
251pub mod map;
252#[cfg(feature = "std")]
253pub mod set;
254
255#[cfg(feature = "std")]
256#[doc(inline)]
257pub use crate::{map::SkipMap, set::SkipSet};