Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Synchronization

Flint’s synchronization model: concurrency is a library feature, not a language syntax feature. There is no async/await, no channel operators, no goroutines. Blocking and non-blocking operations are ordinary function calls.

core/sync: Low-Level Primitives

Atomics

use core/sync

static COUNTER: sync.AtomicU32 = sync.AtomicU32.new(0)

fn on_event() -> () {
    COUNTER.fetch_add(1, sync.Ordering.Relaxed)
}

fn read_count() -> u32 {
    return COUNTER.load(sync.Ordering.Acquire)
}

Available: AtomicU8, AtomicU16, AtomicU32, AtomicBool.

Ordering values: Relaxed, Acquire, Release, AcqRel, SeqCst.

OnceCell<T>: Single-Assignment Global

use core/sync

static DEVICE_ID: sync.OnceCell<u32> = sync.OnceCell.new()

fn init() -> () {
    DEVICE_ID.set(read_chip_id())
}

fn get_id() -> u32 {
    return DEVICE_ID.get().unwrap_or(0)
}

set() is only valid once. Subsequent calls are ignored.

Lazy<T>: On-Demand Initialization

use core/sync

static CALIBRATION: sync.Lazy<Calibration> = sync.Lazy.new(|| -> Calibration {
    return Calibration.load_from_flash()
})

fn use_calibration() -> () {
    let cal = CALIBRATION.get()    // initialized on first call
    apply(cal)
}

Critical Sections

use core/sync

let guard = sync.CriticalSection.enter()
defer guard.exit()
// interrupts disabled, shared state is safe to access
update_shared_flag()

std/sync: Higher-Level Synchronization

Channels

use std/sync

let ends = sync.channel<u32>(16)?   // capacity 16
let tx = ends.tx
let rx = ends.rx

// Producer
tx.send(42)?          // blocking send
tx.try_send(42)?      // non-blocking, returns Err if full

// Consumer
let value = rx.recv()?     // blocking receive
if let Ok(value) = rx.try_recv() {
    process(value)
}

// Shutdown
tx.close()

Mutex

use std/sync

let shared: sync.Mutex<List<u8>> = sync.Mutex.new(List.new())?

fn push_byte(byte: u8) -> Result<(), Error> {
    let mut guard = shared.lock()?
    defer guard.release()
    guard.value.push(byte)?
    return Ok()
}

Semaphore

use std/sync

let sem = sync.Semaphore.new(0)?   // initial count 0

// Signal from ISR or other core
sem.signal()

// Wait in main loop
sem.wait()?

Multicore (RP2040)

use micro/multicore

fn core1_main() -> never {
    loop {
        // secondary core work
    }
}

fn main() -> never {
    multicore.launch(1, core1_main)?
    loop {
        // primary core work
    }
}

ISR Rules

ISR code must not block. In an interrupt handler:

  • Use atomics and OnceCell/Lazy reads: ok.
  • Use try_send and try_recv on channels: ok.
  • Use sync.CriticalSection carefully: ok if brief.
  • Never call recv, send, lock, or wait; these block and are not safe in ISRs.