Sonicsmooth
Sonicsmooth

Reputation: 2777

Arc vs. Orc and threads

I'm using Nim 2.2.0 on Win64

I have a gui program using wnim.

Main thread has some data.

I click a button and launch a long-running thread, passing a ptr to the data, since nim doesn't allow shared data across threads, and for performance I don't want to pass this potentially large data through channels where it would be copied. Button onClick in main thread returns very fast after launching worker thread and allows main thread to continue to be responsive to user input, e.g., moving window, etc.

Main thread and worker thread protect access to data with global Locks, and compiler has been satisfied with appropriate {.gcsafe.} pragmas when worker thread calls closure created in main thread. All closure functions that are created on the stack in main thread (in onClick) and are expected to be alive after stack is invalid (when onClick is done) have {.closure.} pragma. Not sure if this is needed. These closures passed are args to worker thread, and it's these closures that access the main thread's ptr data. Worker thread posts messages to main thread in each loop and waits for acknowledgement, which is sent from main thread after onPaint.

After click, main thread only reads data (during onPaint); worker thread reads and writes data.

First time I click, it works fine. Second or third time, or clicking multiple times before it's done, program crashes at the end of the thread.

If I change to --mm:arc the problem goes away and it runs fine. I can launch multiple threads with multiple clicks before the previous one is done and everything writes over everything else, but at least it doesn't crash.

Looks like orc is better with cycles, but it's the default so am I to presume its "better"? Should I try to figure out why my program crashes with orc but not with arc? Is choosing arc to avoid a crash a valid route? Or is it cheating?

Pseudo code:

Main thread:

bigData: ref Table[string, stuff]
globalLock: Lock
globalSendChan: Channel[string]
globalAckChan:  Channel[bool]

initBigData()
initLock(globalLock)
open(globalSendChan)
open(globalAckChan)

proc onClick() = 
  innerFn = proc() {.closure.} =
     mutateBigDataTakeLongTime(bigData)
  threadArg: tuple[...] = (ptr bigData, innerFn)
  globalThread.createThread(workerFn, threadArg)

proc onPaint() =
  wDC dc
  if not tryAcquire(globalLock):
    return
  # paint using bigData
  release(globalLock)

ackCnt:int
proc onAlgUpdate(event: wEvent) =
  let (msgAvail, msg) = globalSendChan.tryRecv()
  if msgAvail:
    # do stuff with msg
  withLock(globalLock):
    self.refresh()
    RedrawWindow(self.mHwnd)
  gAckChan.send(ackCnt)
  inc ackCnt

USER_ALG_UPDATE do(event:wEvent) onAlgUpdate(event)
    

Worker thread:

proc workerFn(threadArg: tuple[...]) {.thread.} =
  for loop:
    withLock(globalLock):
      {.gcsafe.} threadArg.innerFn()
      doOtherStuff(threadArg.bigdata[]...)
    PostMessage(mainWindow.mHwnd, USER_ALG_UPDATE, 0, 0)
    discard globalAckChan.recv() 

Also, is there a way to check for cycles?, as this appears to be part of the arc-vs-orc narrative. Does this have to do with orc's determinism?

thanks

Upvotes: 0

Views: 25

Answers (0)

Related Questions