There are multiple topics to cover: 1. Sharable memory between threads 2. Safely reclaiming that memory 3. Synchronizing primitives
## Sharable memory between threads. To create memory that can be shared, you need to ensure that the memory backing the data processed in another thread is valid through that processing. You can ensure that with: * Allocation, anywhere, stack, local heap or shared heap (any GC) if your data is plain old data (no ref/seq/strings) AND the parallel processing is guaranteed to finish before the function ends (via blocking). This is called structured parallelism, async/concurrency has reached a similar conclusion more recently (<https://en.wikipedia.org/wiki/Structured_concurrency>) * Allocation of plain old data, manually (no ref/seq/strings) via allocShared or allocShared0 (any GC). Deallocation is done manually as well, this is basically C mode. * If using seq/strings that are shared between threads, you can get away with using any GC, if you only mutate existing data, but not the metadata, i.e. you need to deal with creation and append in a single thread, but mutating existing data from any thread is fine, providing proper synchronization. * The most flexible for shared ref/seq/strings is using a GC with a global heap instead of thread-local heaps: `--arc`, `--orc`, `--boehm`. But similar to 1, while many can access it, one thread is the owner, initially the creator. The owner is the data allocator and responsible for future deallocation, but for ref/seqs/strings the owner can change (using channels). ## Safely reclaiming memory 1. If you use structured parallelism with plain old data and only do multithreading within a function that blocks until parallel processing is finished, all memory reclamation is automatic. 2. If you use manual memory management, make sure that memory is deallocated at the end, and also that deallocation only occurs at the end. You can use Nim's atomic refcount at <https://github.com/nim-lang/threading/blob/49562fa/threading/smartptrs.nim#L77-L132> if needed. 3. Similar to 1 4. Similar to 1 ## Synchronization You can get away with no synchronization primitive if your algorithm is guaranteed to never touch the same memory at the same time, typically this is a parallel for. This is called Data Parallelism. When memory might cause conflict, you can use a lock to ensure consistent ordering of operations and avoid corruption. Lastly, you can also ensure that any one point in time, only one thread has visibility on an object, its owner, and ownership is passed from "service" to "service" each running on their threads, basically a thread-level microservice architecture or producer-consumer architecture that communicate via channels.
