diff --git a/docs/examples/multithreading.nim b/docs/examples/multithreading.nim new file mode 100644 index 000000000..7518dd9ad --- /dev/null +++ b/docs/examples/multithreading.nim @@ -0,0 +1,34 @@ +import std/[os] +import chronos +import chronos/threadsync + +proc cleanupThread*() {.gcsafe, raises: [].}= + {.cast(gcsafe).}: + try: + `=destroy`(getThreadDispatcher()) + when defined(gcOrc): + GC_fullCollect() + except Exception as e: + echo "Exception during cleanup: ", e[] + +proc processAsync() {.async.} = + await sleepAsync(1000) # Waiting on an HTTP Request or the like + echo "Done with async work" + +proc threadProc(threadSignal: ThreadSignalPtr) {.thread.} = + block: + asyncSpawn processAsync() # Start some async work + + waitFor threadSignal.wait() # Do async work and then go to sleep until main thread wakes you up + + cleanupThread() # Clean up thread local variables, e.g. dispatcher + +var thread: Thread[ThreadSignalPtr] +let signal = new(ThreadSignalPtr)[] + +createThread(thread, threadProc, signal) +sleep(10) +discard signal.fireSync() + +joinThread(thread) +discard signal.close() \ No newline at end of file diff --git a/docs/src/multithreading_async.md b/docs/src/multithreading_async.md new file mode 100644 index 000000000..6e9331bbe --- /dev/null +++ b/docs/src/multithreading_async.md @@ -0,0 +1,10 @@ +# Multithreading with async event-loops + +At times you may want to run asynchronous work on another thread for whatever reason, +then wait while working through the asynchronous work until you receive a signal to continue. + +This kind of setup can be facilitated by using `threadSync/ThreadSignalPtr` + +```nim +{{#include ../examples/multithreading.nim}} +``` diff --git a/docs/src/threads.md b/docs/src/threads.md index a28590406..6136cba04 100644 --- a/docs/src/threads.md +++ b/docs/src/threads.md @@ -16,3 +16,21 @@ to notify other threads of progress from within an async procedure. ```nim {{#include ../examples/signalling.nim}} ``` + +### Memory management in multithreading scenarios + +Keep in mind that nim _does not clean up thread-variables_ when the thread exits. +This is problematic for thread variables that are ref-types or contain ref-types, as those will not be cleaned up and thus start causing memory leaks. + +This means, that the dispatcher, which is a thread variable and a ref-type, needs to be cleaned up manually. The same is true for the thread-local variables `localInstance` and `utcInstance` of std/times, which will be used if you use any logging library such as chronicles. +Not just that, you will also need to invoke ORC's cycle-collector manually, as chronos' data-structures are at times cyclical and thus need to be collected as well. + +You can do so by calling `=destroy` on it and forcing ORC to do a collect of cyclical data-structures like so: + +```nim +`=destroy`(times.localInstance) +`=destroy`(times.utcInstance) +`=destroy`(getThreadDispatcher()) +when defined(gcOrc): + GC_fullCollect() +```