Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

#542 Add first draft for chronos-multithreading docs #543

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
34 changes: 34 additions & 0 deletions docs/examples/multithreading.nim
Original file line number Diff line number Diff line change
@@ -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()
10 changes: 10 additions & 0 deletions docs/src/multithreading_async.md
Original file line number Diff line number Diff line change
@@ -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}}
```
18 changes: 18 additions & 0 deletions docs/src/threads.md
Original file line number Diff line number Diff line change
Expand Up @@ -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()
```