#thread #synchronization #atomic #memory #cross-platform #sync

no-std rendezvous_swap

Swap data and sync execution between a pair of threads efficiently

1 unstable release

0.1.0 Mar 16, 2023

#922 in Concurrency

Download history 29/week @ 2024-02-25 47/week @ 2024-03-03

76 downloads per month

GPL-3.0 license

18KB
142 lines

rendezvous_swap

A rendezvous is an execution barrier between a pair of threads, but this crate also provides the option of swapping data at the synchronisation point. (Terminology is from The Little Book of Semaphores)

This is mainly intended for situations where threads sync frequently. Unlike a normal spinlock, it does not use any CAS instructions, just Acquire loads and Release stores which means it can compile to just a handful of non atomic instructions on x86_64. Because the crate uses atomics for synchronisation, it is also no_std.

Data is internally swapped with pointers, so large structures are not costly to swap and therefore do not need to be boxed.

In microbenchmarks on a i5-7200U CPU, it takes less than 100 ns to swap data.

Safety

RendezvousData contains unsafe but all tests pass when running with Miri.

Example: Sync thread execution

use rendezvous_swap::Rendezvous;
use std::thread;

let (mut my_rendezvous, mut their_rendezvous) = Rendezvous::new();
thread::spawn(move || {
    for i in 1..5 {
        println!("{i}");
        their_rendezvous.wait();
    }
});
for i in 1..5 {
    println!("{i}");
    my_rendezvous.wait();
}

This prints:

1
1
2
2
3
3
4
4

Example: Swap thread data

use std::thread;
use rendezvous_swap::RendezvousData;

let (mut my_rendezvous, mut their_rendezvous) = RendezvousData::new(0, 0);
let handle = thread::spawn(move || {
    let borrow = their_rendezvous.swap();
    *borrow = 3;

    let borrow = their_rendezvous.swap();
    assert_eq!(7, *borrow);
});
let borrow = my_rendezvous.swap();
*borrow = 7;

let borrowed_data = my_rendezvous.swap();
assert_eq!(3, *borrowed_data);

Example: Safety

The following won't compile due to the limited lifetime of the references provided by RendezvousData::swap, you will get the familiar lifetime errors as if you are borrowing a struct element. This crate is safe because it is not possible for both threads to have mutable references to the same memory location at the same time.

use std::thread;
use rendezvous_swap::RendezvousData;

let (mut my_rendezvous, mut their_rendezvous) = RendezvousData::new(0, 0);
let handle = thread::spawn(move || {
    their_rendezvous.swap(); // swap return values can be ignored
    their_rendezvous.swap();
});
let old_borrow = my_rendezvous.swap(); // first mutable borrow occurs here

let new_borrow = my_rendezvous.swap(); // second mutable borrow occurs here

*old_borrow = 3; // first borrow is later used here

Current version: 0.1.0

License: GPL-3.0

Dependencies