You can implement atomic refcounting with destructors. See what I do in my multithreading runtime:
type
FlowEvent* = object
e: EventPtr
EventPtr = ptr object
refCount: Atomic[int32]
kind: EventKind
union: EventUnion
# Refcounting is started from 0 and we avoid fetchSub with release semantics
# in the common case of only one reference being live.
proc `=destroy`*(event: var FlowEvent) =
if event.e.isNil:
return
let count = event.e.refCount.load(moRelaxed)
fence(moAcquire)
if count == 0:
# We have the last reference
if not event.e.isNil:
if event.e.kind == Iteration:
wv_free(event.e.union.iter.singles)
# Return memory to the memory pool
recycle(event.e)
else:
discard fetchSub(event.e.refCount, 1, moRelease)
event.e = nil
proc `=sink`*(dst: var FlowEvent, src: FlowEvent) {.inline.} =
# Don't pay for atomic refcounting when compiler can prove there is no ref change
`=destroy`(dst)
system.`=sink`(dst.e, src.e)
proc `=`*(dst: var FlowEvent, src: FlowEvent) {.inline.} =
`=destroy`(dst)
discard fetchAdd(src.e.refCount, 1, moRelaxed)
dst.e = src.e
Multithreaded garbage collection is a very hard problem and it took years for Java to nail it.
The parallel statement has a proof-of-concept array bound checking algorithm that can prove at compile-time that arrays/seq accesses are safe to be made concurrently because the cells are not updated by different threads. It's mostly a proof-of-concept though and the future Z3 integration (a SMT solver) is in part motivated to extend this capability as far as I know.
If you compile with --gc:boehm or --gc:arc memory is allocated on a shared heap. Though I'm currently having trouble with --gc:arc + --threads:on its main motivation is easing multithreading.
Note that for data structures shared between threads you will still need to handle them via destructors + atomic refcounting, there is no plan to add atomic overhead to all types managed by the GC.
Furthermore Nim encourages using message-passing (i.e. share-memory by communicating instead of communicating by sharing).