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