1 stable release

1.0.0 Apr 22, 2023

#404 in Concurrency

MIT license

40KB
779 lines

README

Yaambo

Yaambo is a library that contains an implementation of a concurrent skip list.

A concurrent skip list is a multithreaded implementation of the skip list data structure. It is a probabilistic data structure that allows for fast insertion, deletion, and lookup of elements. It is also efficient in terms of space usage.

A skip list is a linked list with a number of levels. Each level is a linked list of nodes, where each node contains a key and a value. The keys are sorted in ascending order. The higher levels of the skip list skip over a large number of nodes, which makes lookup operations very efficient.

A concurrent skip list uses hand-over-hand locking to protect the nodes in the skip list. This means that only one thread can be modifying a node at a time. However, multiple threads can be reading the nodes at the same time.

The concurrent skip list is a good choice for applications that need to be able to handle a large number of concurrent operations. It is also a good choice for applications that need to be able to perform fast lookup operations.

Here are some of the benefits of using a concurrent skip list:

  • Fast insertion, deletion, and lookup of elements
  • Efficient in terms of space usage
  • Scalable to handle a large number of concurrent operations
  • Good for applications that need to perform fast lookup operations

Here are some of the limitations of using a concurrent skip list:

Can be more complex to implement than other data structures

  • May not be as efficient as other data structures for some operations, such as range queries
  • May not be as scalable as other data structures for some applications

This implementation is based on the on documented in William Pugh's 1989, paper "Concurrent Maintenance of Skip Lists".

Examples

The following examples are adapted from the examples in the Rust standard library documentation for HashMap.

use yaambo::ConcurrentSkipList;

let mut movie_reviews: ConcurrentSkipList<String, String> = ConcurrentSkipList::new();

// Review some movies.
movie_reviews.set(
    "The Matrix".to_string(),
    "My favorite movie.".to_string(),
);
movie_reviews.set(
    "Liquorice Pizza".to_string(),
    "Not for me.".to_string(),
);
movie_reviews.set(
    "Moulin Rouge".to_string(),
    "A guilty pleasure.".to_string(),
);
movie_reviews.set(
    "The Power of the Dog".to_string(),
    "Underroted.".to_string(),
);

// Check for a specific one.
// When collections store owned values (String), they can still be
// queried using references (&str).
if !movie_reviews.contains("Les Misérables") {
    println!("We've got {} reviews, but Les Misérables ain't one.",
             movie_reviews.iter().count());
}

// oops, this review has a lot of spelling mistakes, let's delete it.
movie_reviews.remove("The Power of the Dog");

// Look up the values associated with some keys.
let to_find = ["Moulin Rouge", "Alice's Adventure in Wonderland"];
for &movie in &to_find {
    match movie_reviews.get(movie) {
        Some(review) => println!("{movie}: {review}"),
        None => println!("{movie} is un-reviewed.")
    }
}

// Iterate over everything.
for (movie, review) in &movie_reviews {
    println!("{movie}: \"{review}\"");
}

A ConcurrentSkipList with a known list of items can be initialized from an array:

use yaambo::ConcurrentSkipList;

let solar_distance: ConcurrentSkipList<String, f32> = ConcurrentSkipList::from([
    ("Mercury", 0.4),
    ("Venus", 0.7),
    ("Earth", 1.0),
    ("Mars", 1.5),
]);

The easiest way to use ConcurrentSkipList with a custom key type is to derive [Ord]. This requires that the key derive PartialEq, [Eq], and PartialOrd as well.

use yaambo::ConcurrentSkipList;

#[derive(Debug, PartialEq, Eq, PartialOrd, Ord)]
struct StreetFighter {
    name: String,
    country: String,
}

impl StreetFighter {
    Creates a new Street Fighter.
    fn new(name: &str, country: &str) -> StreetFighter {
        StreetFighter { name: name.to_string(), country: country.to_string() }
    }
}

// Use a ConcurrentSkipList to store the fighters' health points.
let fighters: ConcurrentSkipList<StreetFighter, usize> = ConcurrentSkipList::from([
    (StreetFighter::new("Akuma", "Japan"), 900_usize),
    (StreetFighter::new("Zangief", "Russia"), 1075_usize),
    (StreetFighter::new("Chun Li", "China"), 975_usize),
]);

// Use derived implementation to print the status of the fighters.
for (fighter, health) in &fighters {
    println!("{fighter:?} has {health} hp");
}

License

This library uses the MIT License. If you find it helpful, I’d love to hear about it!

No runtime deps