From b765b41d507dc9355b1893a9b972f67f85293e1e Mon Sep 17 00:00:00 2001 From: twetzel59 Date: Fri, 10 Aug 2018 23:41:17 -0400 Subject: [PATCH 1/5] Add channels example Added an example that shows how to use channels in a typical multithreaded scenario. --- lib/system/channels.nim | 92 +++++++++++++++++++++++++++++++++++++++++ 1 file changed, 92 insertions(+) diff --git a/lib/system/channels.nim b/lib/system/channels.nim index 84aa4306e4f32..07d67895ebef4 100644 --- a/lib/system/channels.nim +++ b/lib/system/channels.nim @@ -20,6 +20,98 @@ ## ## **Note:** Channels cannot be passed between threads. Use globals or pass ## them by `ptr`. +## +## 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``. +## +## .. code-block :: Nim +## # For simple and efficient multithreading. +## # Be sure to compile with --threads:on +## import os, threadpool +## +## # 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. +## var chan: Channel[string] +## +## # This proc will be run in another thread +## # using the threadpool 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). +## proc secondWorker() = +## sleep(2000) +## chan.send("Another message") +## +## # Initialize the channel. +## chan.open() +## +## # Launch the worker. +## spawn firstWorker() +## +## # Block until the message arrives, +## # then print it out. +## echo chan.recv() # "Hello World!" +## +## # 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. +## 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 +## # message. +## sleep(400) +## +## # 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:: +## 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 when not declared(ThisIsSystem): {.error: "You must not import this module explicitly".} From 58279271b872d33893d48f7c850a44f9753f3e65 Mon Sep 17 00:00:00 2001 From: chr Date: Sun, 19 Jan 2020 19:37:29 -0800 Subject: [PATCH 2/5] 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 07d67895ebef4..fd67ef3a91944 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".} From e677c41bedfa5666fc44de2001ef42a9b432d1d5 Mon Sep 17 00:00:00 2001 From: "chr v1.x" Date: Mon, 20 Jan 2020 09:14:22 -0800 Subject: [PATCH 3/5] Use assert instead of echo in examples Co-Authored-By: Juan Carlos --- lib/system/channels.nim | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/lib/system/channels.nim b/lib/system/channels.nim index fd67ef3a91944..8dc8f1973d731 100644 --- a/lib/system/channels.nim +++ b/lib/system/channels.nim @@ -60,7 +60,7 @@ ## createThread(worker1, firstWorker) ## ## # Block until the message arrives, then print it out. -## echo chan.recv() # "Hello World!" +## assert chan.recv() == "Hello World!" ## ## # Wait for the thread to exit before moving on to the next example. ## worker1.joinThread() @@ -74,7 +74,7 @@ ## while true: ## let tried = chan.tryRecv() ## if tried.dataAvailable: -## echo tried.msg # "Another message" +## assert tried.msg == "Another message" ## break ## ## echo "Pretend I'm doing useful work..." @@ -450,4 +450,3 @@ proc ready*[TMsg](c: var Channel[TMsg]): bool = ## new messages. var q = cast[PRawChannel](addr(c)) result = q.ready - From 62532f861fc9794b1d0f3c5845487b9eb2b9a397 Mon Sep 17 00:00:00 2001 From: chr Date: Mon, 20 Jan 2020 09:15:21 -0800 Subject: [PATCH 4/5] non-blocking --- lib/system/channels.nim | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/system/channels.nim b/lib/system/channels.nim index 8dc8f1973d731..d5c7488c9d743 100644 --- a/lib/system/channels.nim +++ b/lib/system/channels.nim @@ -68,7 +68,7 @@ ## # Launch the other worker. ## var worker2: Thread[void] ## createThread(worker2, secondWorker) -## # This time, use a nonblocking approach with tryRecv. +## # This time, use a non-blocking 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: From e291f2df4854aa5d08e05e6afe343a39e34cd826 Mon Sep 17 00:00:00 2001 From: chr Date: Mon, 20 Jan 2020 10:31:11 -0800 Subject: [PATCH 5/5] Revert "Use assert instead of echo in examples" This reverts commit e677c41bedfa5666fc44de2001ef42a9b432d1d5. --- lib/system/channels.nim | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/lib/system/channels.nim b/lib/system/channels.nim index d5c7488c9d743..d51659394dbb1 100644 --- a/lib/system/channels.nim +++ b/lib/system/channels.nim @@ -60,7 +60,7 @@ ## createThread(worker1, firstWorker) ## ## # Block until the message arrives, then print it out. -## assert chan.recv() == "Hello World!" +## echo chan.recv() # "Hello World!" ## ## # Wait for the thread to exit before moving on to the next example. ## worker1.joinThread() @@ -74,7 +74,7 @@ ## while true: ## let tried = chan.tryRecv() ## if tried.dataAvailable: -## assert tried.msg == "Another message" +## echo tried.msg # "Another message" ## break ## ## echo "Pretend I'm doing useful work..." @@ -450,3 +450,4 @@ proc ready*[TMsg](c: var Channel[TMsg]): bool = ## new messages. var q = cast[PRawChannel](addr(c)) result = q.ready +