Hi. I thought I understood how the local heap in Nim works, but this simple example shows that I don't (it throw an exception). :( What am I doing/thinking wrong?
var myLocalThreadID = -1
proc initThreadID*(tid: int): void =
## Initialises the ThreadID. Can only be called once!
if myLocalThreadID != -1:
raise newException(Exception, "ThreadID already initialised: " & $myLocalThreadID)
myLocalThreadID = tid
when isMainModule:
initThreadID(0)
# ...
proc receiver() {.thread.} =
initThreadID(1)
# ...
echo("receiver done!")
var thread: Thread[void]
createThread[void](thread, receiver)
joinThread(thread)
echo("main done!")
My assumption was that every thread, including the main thread, should get it's own copy of "myLocalThreadID" (because it's not {.global.}), safely initialized to "-1" for each thread. This test proves me wrong. So either "myLocalThreadID" is not local, or it is not initialised to "-1" (or something else even stranger?).
Since it cannot run in the Forum, presumably due to it's use of threads, here is the output:
Traceback (most recent call last)
test_mt.nim(14) receiver
test_mt.nim(6) initThreadID
Error: unhandled exception: ThreadID already initialised: 0 [Exception]
Error: execution of an external program failed: '../bin/test_mt '
The terminal process terminated with exit code: 1
EDIT: I've changed the test, to not only see if the "background thread" sees the change of the "main thread", but also if the "main thread" sees the change of the "background thread", and it does! So, as far as I can tell, myLocalThreadID, which is not global, behaves just like if it was global (assuming we're "lucky" and see changes across threads, even without synchronisation).
var myLocalThreadID = -1
proc initThreadID*(tid: int): void =
## Initialises the ThreadID. Can only be called once!
#if myLocalThreadID != -1:
# raise newException(Exception, "ThreadID already initialised: " & $myLocalThreadID)
myLocalThreadID = tid
when isMainModule:
initThreadID(0)
# ...
proc receiver() {.thread.} =
initThreadID(1)
# ...
echo("receiver done!")
var thread: Thread[void]
createThread[void](thread, receiver)
joinThread(thread)
echo("main done! " & $myLocalThreadID)
Output:
receiver done!
main done! 1
My understanding is that every top-level variable in nim is global by default.
The {.global.} pragma has a different semantic, it's used to promote locally-declared variables to the global scope/lifecycle (while restricting their access to the local scope) so they are not discarded after the local scope is discarded. There's a good example here: https://nim-lang.org/docs/manual.html#pragmas-global-pragma
It sounds like what you want is a {.threadvar.} instead: https://nim-lang.org/docs/manual.html#threads-threadvar-pragma
In addition to what boia01 said, there already is system.getThreadId.
I'm not saying it's a bad design, but I'm saying it's a surprising one. Top-level variables are normally global in the languages I know. But that obviously works together with allocation also being global as well. So I guess I was imagining that if allocation was local (by default), then top-level should be local too, so that it "fits together".
I see what you're saying... but Nim's design is different.
There is no invisible synchronization for globals. They are unsafe if you mutate them (usual "shared xor mutable" disclaimer). If you are going to mutate shared state, you must take appropriate precautions (locks, memory barriers, etc.)
Globals are "bad" by design. My understanding is they are provided as a convenience, instead of passing around shared context around all your application.
Globals are (typically) initialized at startup and therefore differ from other thread-specific allocations in that their lifetime is the scope of the application (ie.., globals aren't owned by any thread.)
It's also unsafe to put any thread-owned GC'ed object into a global for that reason. If the thread ends, the object would be reclaimed and you'd be left with a potential illegal access. Instead, if you want to dynamically store things in a global, you currently have to use manual memory management (e.g. allocShared) in addition to thread-safety precautions.