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.

Reply via email to