Skip to content

multiplyco/conc

Repository files navigation

Conc

Cross-platform concurrency primitives for Clojure and ClojureScript.

All primitives expose a unified macro API that compiles to lock-free atomics on the JVM and direct mutation on ClojureScript. The API and semantics are identical across platforms, with the only difference being that JVM operations are thread-safe while ClojureScript operations are single-threaded.

Installation

;; deps.edn
co.multiply/conc {:mvn/version "0.1.3"}

Deque

co.multiply.conc.deque — a lock-free doubly-linked deque with eager folding.

  • O(1) add to tail, O(1) removal by reference, O(1) poll from head
  • Links track their state: pending, live (in a deque), or removed
  • A link can only be in one deque at a time and cannot be re-added after removal
  • Eager folding on removal splices out removed nodes, keeping traversal fast
  • Implements Counted (both platforms), Iterable (JVM), ISeqable/IIterable (CLJS)
(require '[co.multiply.conc.deque :as d])

(let [dq (d/deque)
      a  (my-link)
      b  (my-link)]
  (d/add dq a)
  (d/add dq b)
  (d/size dq)      ;=> 2
  (d/remove dq a)
  (d/poll dq)      ;=> b
  (d/isEmpty dq))  ;=> true

The forEach macro provides a convenient way to walk all live links:

(d/forEach [link dq]
  (println link))

Queue

co.multiply.conc.queue — a concurrent queue backed by the Deque, with O(1) removal.

  • Values are wrapped internally; the queue API deals in values, not links
  • add returns a link that can be used for O(1) removal via remove
  • poll returns the first value (FIFO order), or nil if empty
  • Null/nil values are not permitted (poll uses nil to indicate empty)
  • Implements Counted, Iterable/ISeqable/IIterable
(require '[co.multiply.conc.queue :as q])

(let [queue (q/queue)
      link  (q/add queue :task-1)]
  (q/add queue :task-2)
  (q/add queue :task-3)
  (q/poll queue)     ;=> :task-1
  (q/remove link)    ;=> false (already polled)
  (vec queue))       ;=> [:task-2 :task-3]

The returned link enables cancellation-style patterns — enqueue a task, then remove it before it's processed if it's no longer needed:

(let [queue (q/queue)
      link  (q/add queue :expensive-task)]
  ;; ... conditions change ...
  (q/remove link)   ;=> true (removed before poll)
  (q/poll queue))   ;=> nil

Set

co.multiply.conc.set — a concurrent set backed by ConcurrentHashMap (JVM) or a Clojure set (CLJS).

  • O(1) add, remove, and contains operations
  • Implements java.util.Set on the JVM for full collections interop
  • Implements Counted, Iterable/ISeqable/IIterable
(require '[co.multiply.conc.set :as s])

(let [set (s/set)]
  (s/add set :a)       ;=> true
  (s/add set :b)       ;=> true
  (s/add set :a)       ;=> false (already present)
  (s/contains? set :a) ;=> true
  (s/remove set :a)    ;=> true
  (s/size set)         ;=> 1
  (vec set))           ;=> [:b]

Integer

co.multiply.conc.integer — a cross-platform atomic integer.

  • Backed by AtomicInteger on the JVM, mutable integer on ClojureScript
  • Supports increment, decrement, get, compareAndSet, and update-with-function
  • All operations return int values (not boxed)
(require '[co.multiply.conc.integer :as i])

(let [counter (i/integer 0)]
  (i/incrementAndGet counter)              ;=> 1
  (i/getAndIncrement counter)              ;=> 1 (returns old value)
  (i/getValue counter)                     ;=> 2
  (i/updateAndGet counter #(* % 10))       ;=> 20
  (i/compareAndSet counter 20 0))          ;=> true

Platform differences

On the JVM, all primitives are thread-safe. Deque and Queue use VarHandle CAS operations for lock-free concurrency. Set is backed by ConcurrentHashMap. Integer is backed by AtomicInteger.

On ClojureScript, the same API is available but operations use direct field mutation (single-threaded). This provides the same algorithmic complexity without the overhead of atomic operations.

License

Eclipse Public License 2.0. Copyright (c) 2025 Multiply. See LICENSE.

About

Concurrency primitives

Resources

License

Stars

0 stars

Watchers

0 watching

Forks

Packages

 
 
 

Contributors