#synchronization-primitive #async #send-sync #non-blocking #io #future

no-std unsync

Unsynchronized synchronization primitives for async Rust

2 releases

0.1.2 Mar 22, 2023
0.1.1 Apr 13, 2022
0.1.0 Apr 13, 2022

#1190 in Asynchronous

48 downloads per month

MIT/Apache

93KB
1.5K SLoC

unsync

github crates.io docs.rs build status

Unsynchronized synchronization primitives for async Rust.

This crate provides a fairly simple set of synchronization primitives which are explicitly !Send and !Sync. This makes them useful for use in singlethreaded systems like yew.

You can think of this as a modern replacement to the now-deprecated futures::unsync module.


Why do you want !Send / !Sync synchronization primitives?

Having unsynchronized sync primitives might seem weird since they are largely used for inter-task communication across threads.

The need for such primitives increase as we are seeing more singlethreaded systems like yew mature and are receiving richer support for async programming. In order to make them as efficient as possible it's useful that they are written with unsynchronized systems and constraints in mind so that they don't have to make use of atomics and locks.

The overhead of synchronization in real systems should be minor because of the role channels play in typical applications and they are optimized for uncontended use. But unsynchronized code still has the ability to optimize better for both performance and size.

In one of my applications replacing the use of tokio::sync::oneshot with unsync::oneshot reduced the size of the resulting binary by 30kb (10kb when optimized with wasm-opt). Synthetic benchmarks in this project hints at the unsync channels being about twice as fast for optimized builds when used inside of a LocalSet.

I haven't dug too deep into the specifics of why this is, but as long as this is the case I'd like to have access to drop in replacements allowing someone to tap into these benefits.


Usage

Add the following to your Cargo.toml:

unsync = "0.1.1"

Examples

use unsync::spsc::{channel, Sender, Receiver};
use std::error::Error;
use tokio::task;

async fn receiver(mut rx: Receiver<u32>) -> Vec<u32> {
    let mut out = Vec::new();

    while let Some(m) = rx.recv().await {
        out.push(m);
    }

    out
}

async fn sender(mut tx: Sender<u32>) -> Result<(), Box<dyn Error>> {
    for n in 0..1000 {
        tx.send(n).await?;
    }

    Ok(())
}

async fn run() -> Result<(), Box<dyn Error>> {
    let (tx, rx) = channel(4);

    let _ = task::spawn_local(sender(tx));
    let out = task::spawn_local(receiver(rx)).await?;

    let expected = (0..1000).collect::<Vec<u32>>();
    assert_eq!(out, expected);
    Ok(())
}

Dependencies

~45KB