Hi all!

So - I always wondered if I understood the memory management in nim correctly. 
I had some surprising moments in the past and I was never sure how to access 
variables, when using threads. And I probably wrote a lot of bad code or used 
unnecessary structures. The documentation said that Nim threads had its own 
heap but I had usually no idea how to handle each heap or how to create 
workarounds. The new memory model "arc/orc" also didn't simplify it, as it just 
documented to work different, faster with a shared heap, but trying to use 
arc/orc sometimes resulted in strange crashes that I didn't had before... So I 
created some sample code - just to try out - and I wanted to share it with you.

Just some early observations - correct me if I'm wrong:

  * when using the standard mm
    * `every thread is using its own heap`, mostly means that objects are 
copied when using "ref". You still can easily access objects when using ptr 
references but should then use `GC_ref` and `GC_unref` to control the object 
lifetime.
    * the destructor is called later when the gc is involved
  * arc/orc
    * no deep copying of objects when using "ref", but accessing shared heap 
objects as "ref" isn't safe if the scope of the object is left before the 
thread stops
    * destructor of objects is called early when leaving the scope - similar as 
it happens for normal stack objects. GC is just used in orc to free cyclic 
objects



And here is the sample code
    
    
    import std/os
    import std/strformat
    
    type
      TestObj* = object
        name*: string
    
    proc getAddr(p: TestObj | pointer): string =
      let intVal = cast[uint64](p.unsafeaddr)
      return $intVal # 0x & $(intVal.toHex()) causes early crash
    
    proc getAddr(p: ref TestObj | ptr TestObj ): string =
      let intVal = cast[uint64](p[].unsafeaddr)
      return $intVal
    
    proc `=destroy`*(x: var TestObj) =
      echo fmt"destroying '{x.name}' {getAddr(x.unsafeaddr)} ({getAddr(x)})"
    
    proc newObj(name: string): ref TestObj =
      new result
      result.name = name
      echo fmt"creating new obj '{result.name}' {getAddr(result)} 
({getAddr(result[])})"
    
    when compileOption("threads"):
      proc threadFunc[T](someObj: T) =
          echo fmt"starting thread with '{someObj.name}' "
          sleep(1_000)
          echo fmt"Accessing '{someObj.name}' {getAddr(someObj)} from thread"
    
    proc main() =
      echo "x will not be deleted and can be accessed safely as pointer"
      echo "y is a ref and could be traced"
      # echo "z is referenced as ptr and access is therefore dangerous"
      echo ""
      let x = newObj("x")
      GC_ref(x) # avoid deletion of x
      let y = newObj("y")
      # let z = newObj("z")
      when compileOption("threads"):
        var thread1: Thread[ptr TestObj]
        var thread2: Thread[ref TestObj]
        var thread3: Thread[ptr TestObj]
        createThread(thread1, threadFunc[ptr TestObj], x[].addr)
        createThread(thread2, threadFunc[ref TestObj], y)
        # createThread(thread3, threadFunc[ptr TestObj], z[].addr)
        sleep(200)
      echo "end of main"
      # GC_unref(x)
    
    when isMainModule:
      main()
      echo "all scope objects destroyed"
      GC_fullCollect()
      sleep(3_000) # wait for threads to finish - not using thread join
    
    
    Run

output of `nim c -r -d:release --threads:on --gc:orc .\gcref.nim` (causes 
access violation on linux):
    
    
    x will not be deleted and can be accessed safely as pointer
    y is a ref and could be traced
    
    creating new obj 'x' 10485840 (6552720)
    creating new obj 'y' 10485872 (6552720)
    starting thread with 'x'
    starting thread with 'y'
    end of main
    destroying 'y' 10485872 (6552736)
    all scope objects destroyed
    Accessing 'x' 10485840 from thread
    Accessing '' 10485872 from thread
    
    
    Run

output of `nim c -r -d:release --threads:on .\gcref.nim` (no access violation, 
as referenced object is different):
    
    
    x will not be deleted and can be accessed safely as pointer
    y is a ref and could be traced
    
    creating new obj 'x' 10481744 (6552464)
    creating new obj 'y' 10481776 (6552464)
    starting thread with 'x'
    starting thread with 'y'
    end of main
    all scope objects destroyed
    destroying 'y' 10481776 (6552656)
    Accessing 'y' 17367120 from thread
    Accessing 'x' 10481744 from thread
    
    
    Run

Reply via email to