Safe concurrent programming with OS threads, atomic types, and synchronization primitives.
Turmeric provides 1:1 OS threads via C11 <threads.h> plus thread-safe abstractions (Arc<T>, Mutex<T>, Atomic<T>) for safe concurrent programming. Integration with Turmeric's ownership model (ref<T>, borrow checking) ensures memory safety.
;; Spawn a new thread
(def result
(thread
(fn []
(println "Hello from thread!")
42)))
;; Block until thread completes and get result
(println (thread-join result)) ; prints 42
Each thread has its own stack and thread-local variables:
;; Declare a thread-local
(thread-local my-tls 42)
;; Get current value (only accessible within this thread)
(thread-local-get my-tls) ; => 42
;; Set current value
(thread-local-set my-tls 100)
Arc<T> (atomic reference counting) enables shared ownership across threads:
;; Create a shared value
(def shared (arc (make-counter 0)))
;; Clone the arc (increments reference count)
(thread
(fn []
(let [my-copy (arc-clone shared)]
(modify-counter my-copy))))
;; Original and clones all point to same value
(println (counter-value shared))
arc-clone increments the count; arc drop decrements atomically.Arc<T> gives shared read-only access. For mutable shared state, wrap in Mutex<T>.Mutex<T> (mutual exclusion) protects mutable shared state:
;; Create a mutex
(def counter (mutex 0))
;; Acquire lock, modify, and release (automatically via defer)
(with-lock counter
(fn [value]
(let [new-val (+ value 1)]
new-val)))
;; Check current value (requires lock)
(let [val (with-lock counter (fn [v] v))]
(println val))
defer).with-lock blocks until available.For read-heavy workloads, use RwLock<T>:
;; Multiple readers or one writer
(def data (rw-lock (vec 1 2 3)))
;; Read lock (multiple threads can hold simultaneously)
(read-lock data
(fn [vec] (println vec)))
;; Write lock (exclusive)
(write-lock data
(fn [vec] (set-vec vec 0 42)))
For simple types, Atomic<T> provides lock-free atomic operations:
;; Atomic integer
(def counter (atomic 0))
;; Atomic load
(println (atomic-load counter)) ; => 0
;; Atomic store
(atomic-store counter 42)
;; Atomic compare-and-swap
(atomic-cas counter 42 100) ; success if value was 42, set to 100
;; Atomic add/sub
(atomic-add counter 5) ; atomically add 5
int, bool, usize, pointer types(Planned for Phase 20+; currently available via FFI.)
Block until a condition is signaled:
(def cond (condition-variable))
;; Thread A: wait for signal
(with-lock mutex
(fn [_]
(condition-wait cond mutex)
(println "woken!")))
;; Thread B: signal the condition
(condition-signal cond)
(Planned for Phase 20; currently available via FFI to libdispatch, thread pool libraries, etc.)
Marker traits control what types can be safely shared:
Send — Type can be moved across thread boundaries. If T : Send, Arc<T> can be cloned and sent to another thread.Sync — Type can be safely shared via &T in multiple threads. If T : Sync, multiple threads can hold &T simultaneously without a Mutex.;; These are Send (safe to move to threads)
int, bool, string, (Pair a b) [Send a, Send b]
;; These are Sync (safe to share via &)
int, bool, Mutex<T> [T : Sync]
;; NOT Sync (require Mutex for shared access)
Rc<T> (thread-local ref counting)
ref<T> (single-thread ownership)
Most library types implement these traits automatically based on their fields.
Turmeric's borrow checker enforces:
;; ERROR: cannot move borrowed reference to thread
(let [x 42]
(thread
(fn []
(println x)))) ; x is borrowed; can't move across boundary
;; OK: clone or use Arc
(let [x (arc 42)]
(thread
(fn []
(println (arc-deref x)))))
(See stdlib once available; currently use FFI channels.)
(def counter (mutex 0))
(for-each (range 10)
(fn [i]
(thread
(fn []
(with-lock counter
(fn [n]
(+ n 1)))))))
(println (with-lock counter (fn [n] n))) ; => 10
(def barrier (barrier-new 3))
(for-each (range 3)
(fn [i]
(thread
(fn []
(println (str "Thread " i " starting"))
(barrier-wait barrier)
(println (str "Thread " i " done"))))))