#thread #channel #async-channel #wait #swapping #free #block

nightly cupchan

Simple async overwriting channel between two threads that is wait & block free by swapping cups around

3 releases

0.1.2 Mar 20, 2022
0.1.1 Mar 20, 2022
0.1.0 Mar 19, 2022

#691 in Concurrency

45 downloads per month

LGPL-2.1 AND MIT AND BSD-2-Clause

17KB
336 lines

cupchan

docs.rs crates.io

Yes cup-chan, please swap my cups around uwu

Simple async overwriting channel between two threads that is wait & block free by swapping cups around

This project came from the need for me to have a thread lazily update some data on another thread without having to wait for mutexes.

How it works

the way this crate accomplishes a wait/block-free channel is by having three "cups" with swappable labels. Each cup is marked as having a specific purpose i.e. writing, storage, and reading. (This marker is stored in an atomic u8).

The writing thread has access to the writing cup, and the reading thread has access to the cup marked as reading. Once the writing thread is ready to update the reading thread, it writes to its cup and calls flush() which switches the writing and storage markers around. (this is a single atomic operation using fetch_update).

For example, the cups could start out like this: <W><S><R>

The writer thread writes something: <S><W><R> - the writer marker has now swaped with the storage marker and a flag is set to tell the reader thread that the storage was updated.

The reader thread wants to check the data, so it checks the update flag. If set, the reader swaps the storage and reader markers. Then the reader looks inside the reader cup for the data.

The system of markers ensures that the writing and reading thread never access the same cup at the same time.

Here is a diagram of all the possible cup states and the relations between them: quiver.

Tests

This crate has been validated with loom

Run tests:

$ cargo test
$ RUSTFLAGS="--cfg loom" cargo test --test loom_test --release

Note to self: If using LOOM flags, make sure to clear checkpoint file after changing code.

Benchmarks

Each benchmark represents 5_000 64-bit integers being sent.

test tests::bench_crossbeam_chan_cap_10 ... bench:     227,330 ns/iter (+/- 53,862)
test tests::bench_crossbeam_chan_cap_3  ... bench:     406,431 ns/iter (+/- 73,856)
test tests::bench_cupchan_greedy        ... bench:     404,699 ns/iter (+/- 36,296)
test tests::bench_cupchan_lazy          ... bench:     102,177 ns/iter (+/- 10,770)
test tests::bench_flume_chan            ... bench:     725,894 ns/iter (+/- 107,237)

This crate is faster than crossbeam & flume for what it is supposed to do (stream updated values lazily). If you just need to move and consume data as fast as possible, use those crates instead.

(The difference between lazy & greedy is that lazy yields the thread after every read).

It still is not as fast as it could be, mostly because of the use of fetch_update instead of cpu intrinsics, if anyone has an idea for how to make this better, ping me on the rust discord (i go by @Zyansheep#8020).

Dependencies

~0–29MB
~363K SLoC