Hi.
I have problem with sharing data between threads and I couldn't find any informations in tutorial and other materials. How to share data between threads in most efficient way in Nim? I found in documentation that channels are slow Note: The current implementation of message passing is slow and does not work with cyclic data structures.. Is there some other more efficient and safe alternatives for inter thread communication?
I wrote simple test program to check how sharing variables work in Nim
import threadpool
import os
import locks
import strutils
var sharedString: string
var lock: TLock
initLock(lock)
proc th1() {.thread.} =
while true:
for i in countup(-12332143, 19231231):
acquire(lock)
sleep(10000) # simulate some work
echo "write var"
sharedString = "foo " & $i
release(lock)
proc th2() {.thread.} =
while true:
acquire(lock)
sleep(10000) # simulate some work
echo "read var"
var local = sharedString + "bar "
release(lock)
proc main =
spawn th1()
spawn th2()
when isMainModule:
echo "<<begin>>"
main()
echo "<<end>>"
But it does not even compile.
nim c -r --verbosity:0 --warnings:off --threads:on "test.nim"
MutiThTest.nim(10, 6) Error: 'th1' is not GC-safe as it accesses 'sharedString' which is a global using GC'ed memory
Is the only safe and recommended way of inter thread communication are channels? Is there other good but performant options? Is there sharing variables between threads even possible in nim, with its memory model?
As of now, the only way to share data between threads works for scalar values and ptr types.
The memory model is no principal obstacle. For example, Eiffel's SCOOP has essentially the same model and does sharing just fine. The same approach could in principle be adopted for Nim also:
type
Fork = separate obj
...
Philosoper = separate obj
left, right: Fork
proc pickup(phil: Philosoper, fork: Fork) =
...
proc putdown(phil: Philosoper, fork: Fork) =
...
proc eat(phil: Philosopher, fork1, fork2: Fork) =
phil.pickup(fork1)
phil.pickup(fork2)
...
phil.putdown(fork1)
phil.putdown(fork2)
proc think(phil: Philosopher) = ...
proc activity(phil: Philosopher) =
while true:
phil.eat(phil.fork1, phil.fork2)
phil.think
proc main() =
const N = 5
var forks: array[1..N, Fork]
var philosophers: array[1..N, Philosopher]
for i in 1..N:
new forks[i]
for i in 1..N:
philosophers[i] = Philosopher(left: forks[i], right: forks[i mod N+1])
for i in 1..N:
start philosophers[i].activity
while true:
sleep(1000)
Here, separate denotes a reference type that lives in a separate data space (different processor, different thread-local heap, etc.). Calls of the form ob.f(...) where ob is of a separate type switch to the dataspace that ob belongs to (and copy non-separate arguments to that data space and the result from it).