Skip to content

Commit

Permalink
Add channels example
Browse files Browse the repository at this point in the history
This PR revives nim-lang#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
 nim-lang#4748. Please let me know if there is anything that needs to be
changed!
  • Loading branch information
chr-1x committed Jan 20, 2020
1 parent b765b41 commit 5827927
Showing 1 changed file with 70 additions and 48 deletions.
118 changes: 70 additions & 48 deletions lib/system/channels.nim
Original file line number Diff line number Diff line change
Expand Up @@ -23,95 +23,117 @@
##
## 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:
## echo tried.msg # "Another message"
## 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...
## Pretend I'm doing useful work...
## 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".}
Expand Down

0 comments on commit 5827927

Please sign in to comment.