import threadpool
{.experimental.}
const cntThreds = 2
var
buff0 = "000"
buff1 = "111"
ptrBuff0 = addr buff0
ptrBuff1 = addr buff1
echo repr(buff0)
echo repr(ptrBuff0)
var
thrs: array [0..cntThreds-1, Thread[int]]
channels: array [0..cntThreds-1, Channel[string]]
proc runThread(nK:int) {.thread.} =
while true:
let msg:string = recv channels[nK]
if(msg == "stop"):
echo msg, nK
break
case nk:
of 0:
(ptrBuff0[]).add(msg)
else:
(ptrBuff1[]).add(msg)
if nK == 0:
echo (ptrBuff0[])
echo repr(ptrBuff0)
echo "leave the thread ",nK
for iK in 0..high(thrs):
open channels[iK]
createThread(thrs[iK], runThread, iK)
var nK = 0
for i in 0 .. 10:
if nK > (cntThreds-1):
nK = 0
channels[nK].send( $i & "; " )
inc(nK)
for iK in 0..high(thrs):
channels[iK].send("stop")
joinThreads(thrs)
for iK in 0..high(thrs):
close channels[iK]
#Now buff0 is nil
echo buff0
After compile and run I have the runtime error: SIGSEGV: Illegal storage access. (Attempt to read from nil?)The problem is that the buff0 and buff1 are being modified.
String types (and sequence types) are implemented as dynamically allocated arrays that are automatically reallocated when space is needed for new data. When (ptrBuff0[]).add(msg) is run, the string may be reallocated, invalidating the pointer.
I would suggest creating the buffer inside the thread function, rather than creating a global value.
@Varriount: Does the relocation from add cause the GC to clean up the original location, which buff0 is still pointing to. I guess I'm missing how relocating the string causes the original pointer to be invalid (unless, as I guessing, the relocation causes the memory to be cleaned.
@alexsad: you can confirm varriount's answer by changing your definition to:
var
buff0 = newString(1000)
buff1 = newString(1000)
ptrBuff0 = addr buff0
ptrBuff1 = addr buff1
buff0.setLen(0)
buff1.setLen(0)
buff0.add("000")
buff1.add("111")
By giving it a large initial capacity, the relocation doesn't happen. (I'm not saying this is the solution, I'm just saying this might help illustrate what's going on).
Thanks, Varriount, karl
@karl: I tried your recommendation:
var
buff0 = newString(1000)
...
buff0.setLen(0)
It works properly without relocation and reallocation. Because by buff0 = newString(1000) was already the memory allocated. But in case if simple buff0 = "" then in the thread procedure runThread is happenning GC clean for the global variable buff0 as soon as exit from procedure runThread. Why?@alexsad, I'm not 100% sure. What I think and what I'm hoping @Varriount can clarify is:
1 - buff0 initially points to 0x500001 (some address) 2 - ptrBuff0 points to 0x500001
At this point, as far as Nim's GC is concerned, there's only 1 reference to 0x500001 (buff0). ptrBuff0 doesn't count as a reference because ptr are untracked.
When you add:
1 - buff0 continues to point to 0x500001 2 - ptrBuff0 points to 0x999993
buff0 doesn't change (it never changes), but ptrBuff0 does, because more space was required. The reason I think buff0 becomes invalid is that, as far as Nim is concerned, there was only 1 reference to 0x500001. "Add" resulted in that reference moving, so Nim freed the memory. It wasn't buff0 that moved, but it doesn't matter what moved. "Add" caused the reference count to go down by 1. I think "untracked" is a good choice of words.
Of course, I could be 100% wrong..so...
But in case if simple buff0 = ""
After this line buff0 is a new string and previous newString(1000) is not in effect for it. You can add yet one echo repr(buff0) after this line to see it.