I've been toying with making a SharedRc[T] wrapper type for using atomic ref's with regular ref objects by hi-jacking the normal ref count.
I hacked together a toy demo https://play.nim-lang.org/#pasty=GlvskysjoRsj
The core idea is to wrap ref's in the SharedRc[T] and enforce all access to field via accessors. It needs to be recursive so all fields need to also return SharedRc[FieldType] until you get to data which can be copied directly.
Generally the scheme seems to work, but there's lot of ways for ref's to escape to normal ref-counts which would be hard to avoid. Also, you can't pass an object tree like ref JsonObject without grabbing the non-atomic inner object or a proc using generics.
Still it's something I wanted to experiment with.
import ./sharedrc
type
Test* = object
msg*: string
Foo* = ref object
inner*: ref Test
proc `=destroy`*(obj: Test) =
echo "destroying Test obj: ", obj.msg
`=destroy`(obj.msg)
proc `=destroy`*(obj: Bar) =
echo "destroying Bar obj: ", obj.field
`=destroy`(obj.field)
atomicAccessors(Foo)
proc testProc(aref: SharedRc[ref Test]) {.thread.} =
var lref: SharedRc[ref Test] = aref ## atomically inc ref
echo "\nthread: ", lref.msg ## access data
echo "thread done"
proc testThread() =
echo "Test Thread"
var thread: Thread[SharedRc[Foo]]
var t1 = newSharedRc(Foo(inner: (ref Test)(msg: "hello world!")))
var t2 = t1
let t3: SharedRc[ref Test] = t1.inner ## uses accessor
createThread(thread, testProc, t3)
thread.joinThread()
echo "\nmain continue"
echo "t2: ", t2.inner()
testThread()
Hi elcritch,
It's quite easy to trick even ORC by using GC_ref in sender thread and GC_unref in receiver thread. Not sure how safe it is tho !
I've created a Container object to allow this: https://github.com/Alogani/NimGo_multithreadingattempt/blob/main/src/nimgo/private/safecontainer.nim, which must be used inside the awesome SharedPtr (https://github.com/nim-lang/threading/blob/master/threading/smartptrs.nim), without forgetting to use a channel to avoid race condition ;-)
But the standard way is to use std/isolation. This channel implementation uses it : https://github.com/nim-lang/threading/blob/master/threading/channels.nim
It's quite easy to trick even ORC by using GC_ref in sender thread and GC_unref in receiver thread. Not sure how safe it is tho !
ORC/ARC aren't safe against race conditions on the increment/decrement on refs. So the GC_ref/GC_unref can run into race conditions.
You'd want to use wasMoved to skip the ref/unref steps like in threading/channels.nim.
What my SharedRc experiment did was to turn GC_ref and GC_unref into atomic operations by peeking under the hood of ARC/ORC. This would allow sharing a (mutable) ref object between multiple threads. Of course, you'd still have possible data races with the data in the ref object. So SharedRc would need to be used for immutable data or used with locking for mutable data.
The mm:atomicArc flag @Araq mentioned changes ARC to use atomics for every ref count.
I've created a Container object to allow this: https://github.com/Alogani/NimGo_multithreadingattempt/blob/main/src/nimgo/private/safecontainer.nim, which must be used inside the awesome SharedPtr (https://github.com/nim-lang/threading/blob/master/threading/smartptrs.nim), without forgetting to use a channel to avoid race condition ;-)
You probably don't need that as Isolate basically does the heavy lifting. You can always use unsafeIsolate if you know the object isn't shared and ownership can be given to the other side.
ORC/ARC aren't safe against race conditions on the increment/decrement on refs. So the GC_ref/GC_unref can run into race conditions.
How I see, I haven't thought about it. Thanks !