From 58279271b872d33893d48f7c850a44f9753f3e65 Mon Sep 17 00:00:00 2001 From: chr Date: Sun, 19 Jan 2020 19:37:29 -0800 Subject: [PATCH] Add channels example This PR revives #8611 since the channels module still does not have an example of usage. I attempted to fix the outstanding issue that caused the previous PR to be closed, namely implementing the example on top of threads instead of threadpool. I also added an additional example of using sharedAlloc0 to create channel pointers based on the discussion in #4748. Please let me know if there is anything that needs to be changed! --- lib/system/channels.nim | 118 ++++++++++++++++++++++++---------------- 1 file changed, 70 insertions(+), 48 deletions(-) diff --git a/lib/system/channels.nim b/lib/system/channels.nim index 07d67895ebef..fd67ef3a9194 100644 --- a/lib/system/channels.nim +++ b/lib/system/channels.nim @@ -23,67 +23,54 @@ ## ## Example ## ======= -## In this example, a program is created that uses the ``threadpool`` module -## to launch two separate procedures. These procedures send strings through a -## channel to the main thread, which calls ``echo`` on the strings. -## Both blocking and non-blocking receives are demonstrated. -## The channel is declared as a global to avoid the unsafety of -## passing it by raw ``ptr``. +## The following is a simple example of two different ways to use channels: +## blocking and non-blocking. ## ## .. code-block :: Nim -## # For simple and efficient multithreading. -## # Be sure to compile with --threads:on -## import os, threadpool +## # Be sure to compile with --threads:on. +## # The channels and threads modules are part of system and should not be +## # imported. +## import os ## -## # The channels module is part of -## # system and should not be imported. -## -## # Channels can either be declared -## # at the module level or passed -## # to procedures by ptr (raw pointer), -## # which is unsafe. -## -## # Here, a channel is declared -## # at module scope. -## # Channels are generic, and they -## # include support for passing strings -## # between threads. -## # Note that the strings will be -## # deeply copied. +## # Channels can either be: +## # - declared at the module level, or +## # - passed to procedures by ptr (raw pointer) -- see note on safety. +## # +## # For simplicity, in this example a channel is declared at module scope. +## # Channels are generic, and they include support for passing objects between +## # threads. +## # Note that objects passed through channels will be deeply copied. ## var chan: Channel[string] ## -## # This proc will be run in another thread -## # using the threadpool module. +## # This proc will be run in another thread using the threads module. ## proc firstWorker() = ## chan.send("Hello World!") ## -## # This is another proc to run in a -## # background thread. This proc takes -## # a while to send the message since -## # it sleeps for 2 seconds -## # (or 2000 milliseconds). +## # This is another proc to run in a background thread. This proc takes a while +## # to send the message since it sleeps for 2 seconds (or 2000 milliseconds). ## proc secondWorker() = ## sleep(2000) ## chan.send("Another message") ## ## # Initialize the channel. -## chan.open() +## chan.open() ## ## # Launch the worker. -## spawn firstWorker() +## var worker1: Thread[void] +## createThread(worker1, firstWorker) ## -## # Block until the message arrives, -## # then print it out. +## # Block until the message arrives, then print it out. ## echo chan.recv() # "Hello World!" ## +## # Wait for the thread to exit before moving on to the next example. +## worker1.joinThread() +## ## # Launch the other worker. -## spawn secondWorker() -## # This time, use a nonblocking -## # approach with tryRecv. -## # Since the main thread is not blocked, -## # it could be used to perform other -## # useful work while it waits for -## # data to arrive on the channel. +## var worker2: Thread[void] +## createThread(worker2, secondWorker) +## # This time, use a nonblocking approach with tryRecv. +## # Since the main thread is not blocked, it could be used to perform other +## # useful work while it waits for data to arrive on the channel. ## while true: ## let tried = chan.tryRecv() ## if tried.dataAvailable: @@ -91,20 +78,20 @@ ## break ## ## echo "Pretend I'm doing useful work..." -## # For this example, sleep -## # in order not to flood -## # stdout with the above +## # For this example, sleep in order not to flood stdout with the above ## # message. ## sleep(400) ## +## # Wait for the second thread to exit before cleaning up the channel. +## worker2.joinThread() +## ## # Clean up the channel. ## chan.close() ## ## Sample output ## ------------- -## The program could output something similar -## to this, but keep in mind that exact results -## may vary in the real world:: +## The program should output something similar to this, but keep in mind that +## exact results may vary in the real world:: ## Hello World! ## Pretend I'm doing useful work... ## Pretend I'm doing useful work... @@ -112,6 +99,41 @@ ## Pretend I'm doing useful work... ## Pretend I'm doing useful work... ## Another message +## +## Passing Channels Safely +## ---------------------- +## Note that when passing objects to procedures on another thread by pointer +## (for example through a thread's argument), objects created using the default +## allocator will use thread-local, GC-managed memory. Thus it is generally +## safer to store channel objects in global variables (as in the above example), +## in which case they will use a process-wide (thread-safe) shared heap. +## +## However, it is possible to manually allocate shared memory for channels +## using e.g. ``system.allocShared0`` and pass these pointers through thread +## arguments: +## +## .. code-block :: Nim +## proc worker(channel: ptr Channel[string]) = +## let greeting = channel[].recv() +## echo greeting +## +## proc localChannelExample() = +## # Use allocShared0 to allocate some shared-heap memory and zero it. +## # The usual warnings about dealing with raw pointers apply. Exercise caution. +## var channel = cast[ptr Channel[string]]( +## allocShared0(sizeof(Channel[string])) +## ) +## channel[].open() +## # Create a thread which will receive the channel as an argument. +## var thread: Thread[ptr Channel[string]] +## createThread(thread, worker, channel) +## channel[].send("Hello from the main thread!") +## # Clean up resources. +## thread.joinThread() +## channel[].close() +## deallocShared(channel) +## +## localChannelExample() # "Hello from the main thread!" when not declared(ThisIsSystem): {.error: "You must not import this module explicitly".}