Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Added relax strategy parameter #102

Merged
merged 3 commits into from
Mar 15, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 10 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,11 +9,20 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

### Added

### Changed

### Fixed

# [0.8.0] - 2021-03-15

### Added

- `Once::get_unchecked`
- `RelaxStrategy` trait with type parameter on all locks to support switching between relax strategies.

### Changed

### Fixed
- `lock_api1` feature is now named `lock_api`

# [0.7.1] - 2021-01-12

Expand Down
4 changes: 1 addition & 3 deletions Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[package]
name = "spin"
version = "0.7.1"
version = "0.8.0"
authors = [
"Mathijs van de Nes <git@mathijs.vd-nes.nl>",
"John Ericson <git@JohnEricson.me>",
Expand All @@ -15,7 +15,5 @@ description = "Spin-based synchronization primitives"
lock_api_crate = { package = "lock_api", version = "0.4", optional = true }

[features]
default = ["ticket_mutex"]
lock_api = ["lock_api_crate"]
ticket_mutex = []
std = []
4 changes: 2 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -24,8 +24,8 @@ spinlocks. If you have access to `std`, it's likely that the primitives in
- Upgradeable `RwLock` guards
- Guards can be sent and shared between threads
- Guard leaking
- `std` feature to enable yield to the OS scheduler in busy loops
- `Mutex` can become a ticket lock
- Ticket locks
- Different strategies for dealing with contention

## Usage

Expand Down
81 changes: 34 additions & 47 deletions src/barrier.rs
Original file line number Diff line number Diff line change
@@ -1,22 +1,7 @@
//! Synchronization primitive allowing multiple threads to synchronize the
//! beginning of some computation.
//!
//! Implementation adopted the 'Barrier' type of the standard library. See:
//! https://doc.rust-lang.org/std/sync/struct.Barrier.html
//!
//! Copyright 2014 The Rust Project Developers. See the COPYRIGHT
//! file at the top-level directory of this distribution and at
//! http://rust-lang.org/COPYRIGHT.
//!
//! Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
//! http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
//! <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
//! option. This file may not be copied, modified, or distributed
//! except according to those terms.

use core::sync::atomic::spin_loop_hint as cpu_relax;

use crate::Mutex;

use crate::{mutex::Mutex, RelaxStrategy, Spin};

/// A primitive that synchronizes the execution of multiple threads.
///
Expand Down Expand Up @@ -44,8 +29,8 @@ use crate::Mutex;
/// handle.join().unwrap();
/// }
/// ```
pub struct Barrier {
lock: Mutex<BarrierState>,
pub struct Barrier<R = Spin> {
lock: Mutex<BarrierState, R>,
num_threads: usize,
}

Expand All @@ -71,32 +56,7 @@ struct BarrierState {
/// ```
pub struct BarrierWaitResult(bool);

impl Barrier {
/// Creates a new barrier that can block a given number of threads.
///
/// A barrier will block `n`-1 threads which call [`wait`] and then wake up
/// all threads at once when the `n`th thread calls [`wait`]. A Barrier created
/// with n = 0 will behave identically to one created with n = 1.
///
/// [`wait`]: #method.wait
///
/// # Examples
///
/// ```
/// use spin;
///
/// let barrier = spin::Barrier::new(10);
/// ```
pub const fn new(n: usize) -> Barrier {
Barrier {
lock: Mutex::new(BarrierState {
count: 0,
generation_id: 0,
}),
num_threads: n,
}
}

impl<R: RelaxStrategy> Barrier<R> {
/// Blocks the current thread until all threads have rendezvoused here.
///
/// Barriers are re-usable after all threads have rendezvoused once, and can
Expand Down Expand Up @@ -145,7 +105,7 @@ impl Barrier {
while local_gen == lock.generation_id &&
lock.count < self.num_threads {
drop(lock);
cpu_relax();
R::relax();
lock = self.lock.lock();
}
BarrierWaitResult(false)
Expand All @@ -159,6 +119,33 @@ impl Barrier {
}
}

impl<R> Barrier<R> {
/// Creates a new barrier that can block a given number of threads.
///
/// A barrier will block `n`-1 threads which call [`wait`] and then wake up
/// all threads at once when the `n`th thread calls [`wait`]. A Barrier created
/// with n = 0 will behave identically to one created with n = 1.
///
/// [`wait`]: #method.wait
///
/// # Examples
///
/// ```
/// use spin;
///
/// let barrier = spin::Barrier::new(10);
/// ```
pub const fn new(n: usize) -> Self {
Self {
lock: Mutex::new(BarrierState {
count: 0,
generation_id: 0,
}),
num_threads: n,
}
}
}

impl BarrierWaitResult {
/// Returns whether this thread from [`wait`] is the "leader thread".
///
Expand Down Expand Up @@ -187,7 +174,7 @@ mod tests {
use std::sync::Arc;
use std::thread;

use super::Barrier;
type Barrier = super::Barrier;

fn use_barrier(n: usize, barrier: Arc<Barrier>) {
let (tx, rx) = channel();
Expand Down
31 changes: 16 additions & 15 deletions src/lazy.rs
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
//! Synchronization primitives for lazy evaluation.
//!
//! Implementation adapted from the `SyncLazy` type of the standard library. See:
//! https://github.com/rust-lang/rust/blob/cae8bc1f2324e31c98cb32b8ed37032fc9cef405/library/std/src/lazy.rs
//! <https://doc.rust-lang.org/std/lazy/struct.SyncLazy.html>

use core::{cell::Cell, fmt, ops::Deref};
use crate::Once;
use crate::{once::Once, RelaxStrategy, Spin};

/// A value which is initialized on the first access.
///
Expand Down Expand Up @@ -38,12 +38,12 @@ use crate::Once;
/// // Some("Hoyten")
/// }
/// ```
pub struct Lazy<T, F = fn() -> T> {
cell: Once<T>,
pub struct Lazy<T, F = fn() -> T, R = Spin> {
cell: Once<T, R>,
init: Cell<Option<F>>,
}

impl<T: fmt::Debug, F> fmt::Debug for Lazy<T, F> {
impl<T: fmt::Debug, F, R> fmt::Debug for Lazy<T, F, R> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("Lazy").field("cell", &self.cell).field("init", &"..").finish()
}
Expand All @@ -57,15 +57,15 @@ impl<T: fmt::Debug, F> fmt::Debug for Lazy<T, F> {
unsafe impl<T, F: Send> Sync for Lazy<T, F> where Once<T>: Sync {}
// auto-derived `Send` impl is OK.

impl<T, F> Lazy<T, F> {
impl<T, F, R> Lazy<T, F, R> {
/// Creates a new lazy value with the given initializing
/// function.
pub const fn new(f: F) -> Lazy<T, F> {
Lazy { cell: Once::new(), init: Cell::new(Some(f)) }
pub const fn new(f: F) -> Self {
Self { cell: Once::new(), init: Cell::new(Some(f)) }
}
}

impl<T, F: FnOnce() -> T> Lazy<T, F> {
impl<T, F: FnOnce() -> T, R: RelaxStrategy> Lazy<T, F, R> {
/// Forces the evaluation of this lazy value and
/// returns a reference to result. This is equivalent
/// to the `Deref` impl, but is explicit.
Expand All @@ -80,24 +80,25 @@ impl<T, F: FnOnce() -> T> Lazy<T, F> {
/// assert_eq!(Lazy::force(&lazy), &92);
/// assert_eq!(&*lazy, &92);
/// ```
pub fn force(this: &Lazy<T, F>) -> &T {
pub fn force(this: &Self) -> &T {
this.cell.call_once(|| match this.init.take() {
Some(f) => f(),
None => panic!("Lazy instance has previously been poisoned"),
})
}
}

impl<T, F: FnOnce() -> T> Deref for Lazy<T, F> {
impl<T, F: FnOnce() -> T, R: RelaxStrategy> Deref for Lazy<T, F, R> {
type Target = T;

fn deref(&self) -> &T {
Lazy::force(self)
Self::force(self)
}
}

impl<T: Default> Default for Lazy<T> {
impl<T: Default, R> Default for Lazy<T, fn() -> T, R> {
/// Creates a new lazy value using `Default` as the initializing function.
fn default() -> Lazy<T> {
Lazy::new(T::default)
fn default() -> Self {
Self::new(T::default)
}
}
83 changes: 65 additions & 18 deletions src/lib.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
#![cfg_attr(all(not(feature = "std"), not(test)), no_std)]
#![cfg_attr(docsrs, feature(doc_cfg))]
#![deny(missing_docs)]

//! This crate provides [spin-based](https://en.wikipedia.org/wiki/Spinlock) versions of the
Expand All @@ -19,6 +20,10 @@
//!
//! - Guard leaking
//!
//! - Ticket locks
//!
//! - Different strategies for dealing with contention
//!
//! # Relationship with `std::sync`
//!
//! While `spin` is not a drop-in replacement for `std::sync` (and
Expand Down Expand Up @@ -55,43 +60,85 @@
#[cfg(any(test, feature = "std"))]
extern crate core;

// Choose a different relaxation strategy based on whether `std` is available or not.
#[cfg(not(feature = "std"))]
use core::sync::atomic::spin_loop_hint as relax;
#[cfg(feature = "std")]
use std::thread::yield_now as relax;

pub mod barrier;
pub mod lazy;
pub mod mutex;
pub mod once;
pub mod rw_lock;
pub mod relax;

pub use mutex::MutexGuard;
pub use rw_lock::RwLockReadGuard;
pub use relax::{Spin, RelaxStrategy};
#[cfg(feature = "std")]
pub use relax::Yield;

// Avoid confusing inference errors by aliasing away the relax strategy parameter. Users that need to use a different
// relax strategy can do so by accessing the types through their fully-qualified path. This is a little bit horrible
// but sadly adding a default type parameter is *still* a breaking change in Rust (for understandable reasons).

/// A primitive that synchronizes the execution of multiple threads. See [`barrier::Barrier`] for documentation.
///
/// A note for advanced users: this alias exists to avoid subtle type inference errors due to the default relax
/// strategy type parameter. If you need a non-default relax strategy, use the fully-qualified path.
pub type Barrier = crate::barrier::Barrier;

/// A value which is initialized on the first access. See [`lazy::Lazy`] for documentation.
///
/// A note for advanced users: this alias exists to avoid subtle type inference errors due to the default relax
/// strategy type parameter. If you need a non-default relax strategy, use the fully-qualified path.
pub type Lazy<T, F = fn() -> T> = crate::lazy::Lazy<T, F>;

/// A primitive that synchronizes the execution of multiple threads. See [`mutex::Mutex`] for documentation.
///
/// A note for advanced users: this alias exists to avoid subtle type inference errors due to the default relax
/// strategy type parameter. If you need a non-default relax strategy, use the fully-qualified path.
pub type Mutex<T> = crate::mutex::Mutex<T>;

/// A primitive that provides lazy one-time initialization. See [`once::Once`] for documentation.
///
/// A note for advanced users: this alias exists to avoid subtle type inference errors due to the default relax
/// strategy type parameter. If you need a non-default relax strategy, use the fully-qualified path.
pub type Once<T> = crate::once::Once<T>;

/// A lock that provides data access to either one writer or many readers. See [`rw_lock::RwLock`] for documentation.
///
/// A note for advanced users: this alias exists to avoid subtle type inference errors due to the default relax
/// strategy type parameter. If you need a non-default relax strategy, use the fully-qualified path.
pub type RwLock<T> = crate::rw_lock::RwLock<T>;

/// A guard that provides immutable data access but can be upgraded to [`RwLockWriteGuard`]. See
/// [`rw_lock::RwLockUpgradableGuard`] for documentation.
///
/// A note for advanced users: this alias exists to avoid subtle type inference errors due to the default relax
/// strategy type parameter. If you need a non-default relax strategy, use the fully-qualified path.
pub type RwLockUpgradableGuard<'a, T> = crate::rw_lock::RwLockUpgradableGuard<'a, T>;

pub use barrier::Barrier;
pub use lazy::Lazy;
pub use mutex::{Mutex, MutexGuard};
pub use once::Once;
pub use rw_lock::{RwLock, RwLockReadGuard, RwLockWriteGuard, RwLockUpgradableGuard};
/// A guard that provides mutable data access. See [`rw_lock::RwLockWriteGuard`] for documentation.
///
/// A note for advanced users: this alias exists to avoid subtle type inference errors due to the default relax
/// strategy type parameter. If you need a non-default relax strategy, use the fully-qualified path.
pub type RwLockWriteGuard<'a, T> = crate::rw_lock::RwLockWriteGuard<'a, T>;

/// Spin synchronisation primitives, but compatible with [`lock_api`](https://crates.io/crates/lock_api).
#[cfg(feature = "lock_api1")]
#[cfg(feature = "lock_api")]
pub mod lock_api {
/// A lock that provides mutually exclusive data access (compatible with [`lock_api`](https://crates.io/crates/lock_api)).
pub type Mutex<T> = lock_api::Mutex<crate::Mutex<()>, T>;
pub type Mutex<T> = lock_api_crate::Mutex<crate::Mutex<()>, T>;

/// A guard that provides mutable data access (compatible with [`lock_api`](https://crates.io/crates/lock_api)).
pub type MutexGuard<'a, T> = lock_api::MutexGuard<'a, crate::Mutex<()>, T>;
pub type MutexGuard<'a, T> = lock_api_crate::MutexGuard<'a, crate::Mutex<()>, T>;

/// A lock that provides data access to either one writer or many readers (compatible with [`lock_api`](https://crates.io/crates/lock_api)).
pub type RwLock<T> = lock_api::RwLock<crate::RwLock<()>, T>;
pub type RwLock<T> = lock_api_crate::RwLock<crate::RwLock<()>, T>;

/// A guard that provides immutable data access (compatible with [`lock_api`](https://crates.io/crates/lock_api)).
pub type RwLockReadGuard<'a, T> = lock_api::RwLockReadGuard<'a, crate::RwLock<()>, T>;
pub type RwLockReadGuard<'a, T> = lock_api_crate::RwLockReadGuard<'a, crate::RwLock<()>, T>;

/// A guard that provides mutable data access (compatible with [`lock_api`](https://crates.io/crates/lock_api)).
pub type RwLockWriteGuard<'a, T> = lock_api::RwLockWriteGuard<'a, crate::RwLock<()>, T>;
pub type RwLockWriteGuard<'a, T> = lock_api_crate::RwLockWriteGuard<'a, crate::RwLock<()>, T>;

/// A guard that provides immutable data access but can be upgraded to [`RwLockWriteGuard`] (compatible with [`lock_api`](https://crates.io/crates/lock_api)).
pub type RwLockUpgradableReadGuard<'a, T> =
lock_api::RwLockUpgradableReadGuard<'a, crate::RwLock<()>, T>;
lock_api_crate::RwLockUpgradableReadGuard<'a, crate::RwLock<()>, T>;
}
Loading