Here is example. I get SIGSEGV: Illegal storage access. (Attempt to read from nil?) When try to access person in printer
I used wasMoved(param) but still param was somehow cleaned And I don't want to make a copy
import std/strformat
type
Person = object
name: string
age: Natural
PersonRef = ref Person
ThreadData[T] = object
dataFn: proc (arg: sink T) {.thread, nimcall.}
data: ptr T
proc `=copy`(a: var Person, b:Person) {.error.}
proc printer(person: sink PersonRef) {.thread.} =
echo "printer"
echo(fmt"{person.name} is {person.age} years old")
proc wrapper[T](arg: ThreadData[T]) =
echo "wrapper"
arg.dataFn(arg.data[])
proc createMyThread[TArg](tp: proc (arg: sink TArg) {.thread, nimcall.}, param: sink TArg): Thread[ThreadData[TArg]] =
createThread(result, wrapper[TArg], ThreadData[TArg](dataFn: tp, data: addr(param)))
wasMoved(param)
proc main() =
var person = new(PersonRef)
person.name= "hello"
person.age = 12
var aThread = createMyThread[PersonRef](printer, person)
joinThread(aThread)
main()
wasMoved is normally only needed in a few cases and in your example using data: addr(param) won't actually move the data.
Before getting into it I'd say that if you just wanting to call a worker proc on a thread that I'd recommend malebolgia or nim-taskpools as both will handle passing the data for you. You can also use std/tasks which is what they build on.
If you still want to do it manually you can use move (or isolate as @sls1005 mentioned will probably work). I updated your example to work below.
Note that addr(param) will take the address of the memory holding the ref pointer (** in C), not the point to the ref'ed object. Also, ref object types won't copy your data. I added a destroy so you can see when Person is freed.
import std/strformat
type
Person = object
name: string
age: Natural
PersonRef = ref Person
ThreadData[T] = object
dataFn: proc (arg: sink T) {.nimcall, gcsafe.}
data: T
proc `=destroy`*(obj: var Person) =
echo "destroy person"
`=destroy`(obj.name)
proc `=copy`(a: var Person, b:Person) {.error.}
proc printer(person: sink PersonRef) =
echo "printer"
echo(fmt"{person.name} is {person.age} years old")
proc wrapper[T](arg: ThreadData[T]) {.thread.} =
echo "wrapper"
arg.dataFn(arg.data)
proc createMyThread[T](tp: proc (arg: sink T) {.nimcall, gcsafe.},
param: sink T): Thread[ThreadData[T]] =
createThread(result, wrapper[T], ThreadData[T](dataFn: tp, data: move(param)))
assert param.isNil
echo "post thread: ", repr param
proc main() =
var person = PersonRef()
person.name= "hello"
person.age = 12
var aThread = createMyThread[PersonRef](printer, person)
joinThread(aThread)
main()
Not quite safe yet I'm afraid. Run with tsan or helgrind for more details:
nim r --passC:-fsanitize=thread --passL:-fsanitize=thread t.nim
WARNING: ThreadSanitizer: data race (pid=1279003)
Read of size 8 at 0x7f74a5300050 by thread T1:
#0 nimIncRef <null> (t_52B4682E2A1D7D74D3894B631D137AD3A401EA34+0x262b2) (BuildId: c8ed8f65b5429229b278513dd58252894e0ebcab)
#1 eqdup___t_u84 <null> (t_52B4682E2A1D7D74D3894B631D137AD3A401EA34+0x267da) (BuildId: c8ed8f65b5429229b278513dd58252894e0ebcab)
#2 wrapper__t_u150 <null> (t_52B4682E2A1D7D74D3894B631D137AD3A401EA34+0x268fd) (BuildId: c8ed8f65b5429229b278513dd58252894e0ebcab)
#3 threadProcWrapDispatch__t_u207 <null> (t_52B4682E2A1D7D74D3894B631D137AD3A401EA34+0x1b701) (BuildId: c8ed8f65b5429229b278513dd58252894e0ebcab)
#4 threadProcWrapStackFrame__t_u197 <null> (t_52B4682E2A1D7D74D3894B631D137AD3A401EA34+0x1b9ad) (BuildId: c8ed8f65b5429229b278513dd58252894e0ebcab)
#5 threadProcWrapper__t_u183 <null> (t_52B4682E2A1D7D74D3894B631D137AD3A401EA34+0x5e67) (BuildId: c8ed8f65b5429229b278513dd58252894e0ebcab)
Previous write of size 8 at 0x7f74a5300050 by main thread:
#0 nimDecRefIsLast <null> (t_52B4682E2A1D7D74D3894B631D137AD3A401EA34+0x26506) (BuildId: c8ed8f65b5429229b278513dd58252894e0ebcab)
#1 eqdestroy___t_u78 <null> (t_52B4682E2A1D7D74D3894B631D137AD3A401EA34+0x269d2) (BuildId: c8ed8f65b5429229b278513dd58252894e0ebcab)
#2 createMyThread__t_u126 <null> (t_52B4682E2A1D7D74D3894B631D137AD3A401EA34+0x26e60) (BuildId: c8ed8f65b5429229b278513dd58252894e0ebcab)
#3 main__t_u124 <null> (t_52B4682E2A1D7D74D3894B631D137AD3A401EA34+0x278df) (BuildId: c8ed8f65b5429229b278513dd58252894e0ebcab)
#4 NimMainModule <null> (t_52B4682E2A1D7D74D3894B631D137AD3A401EA34+0x27c02) (BuildId: c8ed8f65b5429229b278513dd58252894e0ebcab)
#5 NimMainInner <null> (t_52B4682E2A1D7D74D3894B631D137AD3A401EA34+0x27a7d) (BuildId: c8ed8f65b5429229b278513dd58252894e0ebcab)
#6 NimMain <null> (t_52B4682E2A1D7D74D3894B631D137AD3A401EA34+0x27a9f) (BuildId: c8ed8f65b5429229b278513dd58252894e0ebcab)
#7 main <null> (t_52B4682E2A1D7D74D3894B631D137AD3A401EA34+0x27b18) (BuildId: c8ed8f65b5429229b278513dd58252894e0ebcab)
Thread T1 (tid=1279005, running) created by main thread at:
#0 pthread_create ../../../../src/libsanitizer/tsan/tsan_interceptors_posix.cpp:1036 (libtsan.so.2+0x3c556) (BuildId: 1a698d3723182033582d937a9e38752052e20990)
#1 createThread__t_u162 <null> (t_52B4682E2A1D7D74D3894B631D137AD3A401EA34+0x606e) (BuildId: c8ed8f65b5429229b278513dd58252894e0ebcab)
#2 createMyThread__t_u126 <null> (t_52B4682E2A1D7D74D3894B631D137AD3A401EA34+0x26bbb) (BuildId: c8ed8f65b5429229b278513dd58252894e0ebcab)
#3 main__t_u124 <null> (t_52B4682E2A1D7D74D3894B631D137AD3A401EA34+0x278df) (BuildId: c8ed8f65b5429229b278513dd58252894e0ebcab)
#4 NimMainModule <null> (t_52B4682E2A1D7D74D3894B631D137AD3A401EA34+0x27c02) (BuildId: c8ed8f65b5429229b278513dd58252894e0ebcab)
#5 NimMainInner <null> (t_52B4682E2A1D7D74D3894B631D137AD3A401EA34+0x27a7d) (BuildId: c8ed8f65b5429229b278513dd58252894e0ebcab)
#6 NimMain <null> (t_52B4682E2A1D7D74D3894B631D137AD3A401EA34+0x27a9f) (BuildId: c8ed8f65b5429229b278513dd58252894e0ebcab)
#7 main <null> (t_52B4682E2A1D7D74D3894B631D137AD3A401EA34+0x27b18) (BuildId: c8ed8f65b5429229b278513dd58252894e0ebcab)
In the example in my post it doesn't work
I get error SIGSEGV: Illegal storage access
If I have only ref object, because It was declared only as ref. Is it possible to extract object part programmatically ?
Example
type
PersonRef = ref object
name: string
And get object type
getObjectType(PersonRef) == Person
Can I somehow annotate generic param with ObjectType ?
ThreadData[T] = object
data: ptr T:ObjectType
person = ref Person(..)
threadData = ThreadData[ref Person](data: addr(person[]))
Here is final code. It seems to work
import std/strformat
import std/isolation
type
Person = object
name: string
age: Natural
PersonRef = ref Person
ThreadData[T] = object
dataFn: proc (arg: sink ref T) {.thread, nimcall.}
data: ptr T
# proc `=destroy`(obj: Person) =
# echo "Destroy! Tread: ", getThreadId(), " Object: ", addr(obj).repr
proc `=copy`(a: var Person, b:Person) {.error.}
proc printer(person: sink PersonRef) {.thread.} =
echo(fmt"{person.name} is {person.age} years old")
proc wrapper[T](arg: ThreadData[T]) =
var data = new(T)
data[] = move arg.data[]
arg.dataFn(move data)
wasMoved(data)
proc createMyThread[TArg](tp: proc (arg: sink ref TArg) {.thread, nimcall.}, isolatedParam: sink Isolated[ref TArg]): Thread[ThreadData[TArg]] =
var param = extract(isolatedParam)
createThread(result, wrapper[TArg], ThreadData[TArg](dataFn: tp, data: addr(param[])))
wasMoved(param)
proc main() =
echo "main thread ", getThreadId()
var aThread = createMyThread(printer, isolate(PersonRef(name: "lis", age: 12)))
joinThread(aThread)
main()
@zevv I guess it makes sense it won't pass the thread sanitizer as there's no locking or anything other than the implicit thread creation order which the sanitizer won't know. I totally forgot about the thread sanitizer flags!
@araq good point, it generally works with ARC in certain scenarios, but isn't safe and breaks with ORC.
@c4UlMu You can look at threading/channels to see how the threading library does it. You copy the ref pointer address, then you need to run GC_runOrc and after that call wasMoved. Channel send does take the address of the ref (so **) but uses it to copy the ref pointer into the channel buffer before calling wasMoved on the arg.
Still it'd be easier to use malebolgia, threading/channels, or even std/tasks. I use move for prototype code when I don't wanna bother (yet) with channels or similar, but as @araq mentioned, it's not supported.
If you need to access the data (read only) on multiple threads you can use the threading/smartptrs. Generally the Locker construct from malebolgia will be much less error prone and simpler.
Thank you for link to threading/channels I understand that malebolgia probably very good. And I guess that 99% of tasks can be done with it. But I want to know how to do it myself. Because maybe I or somebody else want to write his own malebolgia. If we always answer: Use malebolgia, how can we get knowledge about low level threads. I've read all documentation and read several articles but this wasn't enough and I had to ask it here. Maybe this thread will be useful to somebody else.
And thanks to everybody. I think now I understand nim's memory management and threads much more