Lock-Free Patterns
Concurrency patterns used in bbx_audio.
Why Lock-Free?
Audio threads cannot wait for locks:
- Fixed time budget per buffer
- Priority inversion risks
- Unpredictable latency
SPSC Ring Buffer
For single-producer single-consumer scenarios:
#![allow(unused)] fn main() { use bbx_core::SpscRingBuffer; let (producer, consumer) = SpscRingBuffer::new::<f32>(1024); // Producer thread producer.try_push(sample); // Consumer thread (audio) if let Some(sample) = consumer.try_pop() { // Process } }
Memory Ordering
#![allow(unused)] fn main() { // Simplified concept impl SpscRingBuffer { fn try_push(&self, item: T) -> bool { let head = self.head.load(Ordering::Relaxed); let tail = self.tail.load(Ordering::Acquire); // Sync with consumer if is_full(head, tail) { return false; } self.buffer[head] = item; self.head.store(next(head), Ordering::Release); // Sync with consumer true } } }
Atomic Parameters
For real-time parameter updates:
#![allow(unused)] fn main() { use std::sync::atomic::{AtomicU32, Ordering}; struct Parameter { value: AtomicU32, } impl Parameter { fn set(&self, value: f32) { self.value.store(value.to_bits(), Ordering::Relaxed); } fn get(&self) -> f32 { f32::from_bits(self.value.load(Ordering::Relaxed)) } } }
When Locks Are OK
Not all code is audio-critical:
- Setup/teardown: Can use locks
- UI thread: Not time-critical
- File I/O: Background threads
Only the audio processing path must be lock-free.