From b7e0d0654c94ef0275c1b449c2f1d57bd2bf533f Mon Sep 17 00:00:00 2001 From: flywind Date: Thu, 11 Mar 2021 09:12:23 +0800 Subject: [PATCH 01/37] Create 2021-03-12-new-Nim-channels.md --- jekyll/_posts/2021-03-12-new-Nim-channels.md | 225 +++++++++++++++++++ 1 file changed, 225 insertions(+) create mode 100644 jekyll/_posts/2021-03-12-new-Nim-channels.md diff --git a/jekyll/_posts/2021-03-12-new-Nim-channels.md b/jekyll/_posts/2021-03-12-new-Nim-channels.md new file mode 100644 index 000000000..582e1f0ee --- /dev/null +++ b/jekyll/_posts/2021-03-12-new-Nim-channels.md @@ -0,0 +1,225 @@ +# The new Nim channels implementation for ORC + +Version 1.4 ships with the so-called ORC memory management algorithm. ORC is the existing ARC algorithm (first shipped in version 1.2) plus a cycle collector. The Nim devel branch also introduces a new module called `std/isolation`. With it we can pass `isolated` data to threads safely and easily. It prevents data races at compile time. Recently `std/channels` is merged to the devel branch which is designed for ORC. It combines `isolated` data and `channels` and is efficient and safe to use. + +**Note:** you need the Nim devel branch to compile the code below. + +## Background + +A channel is a model for sharing memory via message passing. A thread is able to send or receive messages over a channel. It’s like sending a letter to your friend. The postman is the channel. Your friend is the receiver. You may know `system/channels` already exists. What’s the difference between new channels implementation and the old one? If you use the old one, you need to copy your letter by hand first and send the copied one to your friend instead. Then your friend may mark something on the copied letter, it won’t affect the original letter. It works fine, however it is not efficient. If you use the new one, you only need to put your letter in the mailbox. No need to copy your letter! + +## The advantages + +- Designed for ARC/ORC, no legacy code +- No need to `deepcopy`, just move data around +- No data races +- Based on [Michael & Scott lock-based queues](https://www.cs.rochester.edu/~scott/papers/1996_PODC_queues.pdf) + +## Explore the new channels + +**Note:** Be sure to compile your code with `--gc:orc –-threads:on`. + +### Let's crawl the web + +**todo_urls.json** + +```json +{"url": ["https://google.com", "https://nim-lang.org"]} +``` + +**app.nim** + +The main thread prepares tasks via reading `todo_urls.txt`. Then it sends JSON data to a channel. The crawl thread does the crawlers’ work. It receives URL data from the channel and downloads the contents using `httpclient`. + +```nim +import std/channels +import std/[httpclient, isolation, json] + + +var ch = initChan[JsonNode]() # we need to send JsonNode + +proc download(client: HttpClient, url: string) = + let response = client.get(url) + echo "content: " + echo response.body[0 .. 20] # prints the results + +proc crawl = + var client = newHttpClient() # the crawler + var data: JsonNode + ch.recv(data) # the JSON data + if data != nil: + for url in data["url"]: + download(client, url.getStr) + client.close() + +proc prepareTasks(fileWithUrls: string): seq[Isolated[JsonNode]] = + result = @[] + for line in lines(fileWithUrls): + result.add isolate(parseJson(line)) # parse JSON file + +proc spawnCrawlers = + var tasks = prepareTasks("todo_urls.json") + for t in mitems tasks: # we need a mutable view of the items" + ch.send move t + +var thr: Thread[void] +createThread(thr, crawl) # create crawl thread + +spawnCrawlers() +joinThread(thr) +``` + +First you need to import `std/channels`. + +Then you can create a channel using `initChan`. It uses `mpmc` internally which stands for multiple producer, multiple consumer. The `elements` parameter is used to specify whether a channel is buffered or not. For unbuffered channel, the sender and the receiver block until the other side is ready. Sending data to a buffered channel blocks only when the buffer is full. Receiving data from a buffered channel blocks when the buffer is empty. + +`initChan` is a generic proc, you can specify the types of the data you want to send or receive. + +```nim +var chan1 = initChan[int]() +# or +var chan2 = initChan[string](elements = 1) # unbuffered channel +# or +var chan3 = initChan[seq[string]](elements = 30) # buffered channel +``` + +`send` proc takes something we want to send to the channel. The passed data is moved around, not copied. Because `chan.send(isolate(data))` is very common to use, `template send[T](c: var Chan[T]; src: T) = chan.send(isolate(src))` is provided for convenience. For example, you can use `chan.send("Hello World")` instead of `chan.send(isolate("Hello World!"))`. + +There are two useful procs for a receiver: `recv` and `tryRecv`. `recv` blocks until something is sent to the channel. In contrast `tryRecv` doesn’t block. If no message exists in the channel, it just fails and returns `false`. We can write a while loop to call `tryRecv`and handle a message when available. + +### It is safe and convenient + +The Nim compiler rejects the program below at compile time. It says that `expression cannot be isolated: s`. Because s is a ref object, may be modified somewhere and is not unique. So the variable cannot be isolated. + + +```nim +import std/[channels, json, isolation] + +var chan = initChan[JsonNode]() + +proc spawnCrawlers = + var s = newJString("Hello, Nim") + chan.send isolate(s) +``` + +It is only allowed to pass a function call directly. + +```nim +import std/[channels, json, isolation] + +var chan = initChan[JsonNode]() + +proc spawnCrawlers = + chan.send isolate(newJString("Hello, Nim")) +``` + +`Isolated` data can only be moved, not copied. It is implemented as a library without bloating Nim's core type system. The `isolate` proc is used to create an isolated subgraph from the expression `value`. The expression `value` is checked at compile time . The `extract` proc is used to get the internal value of `Isolated` data. + +```nim +import std/isolation + +var data = isolate("string") +doAssert data.extract == "string" +doAssert data.extract == "" +``` + +By means of `Isolated` data, the channels become safe and convenient to use. + + +## Benchmark + +Here is a simple benchmark. We create 10 threads for sending data to the channel and 5 threads for receiving data from the channel. + +```nim +# benchmark the new channel implementation with +# `nim c -r --threads:on --gc:orc -d:newChan -d:danger app.nim` +# +# benchmark the old channel implementation with +# `nim c -r --threads:on -d:oldChan -d:danger app.nim` + +import std/[os, times, isolation] + +var + sender: array[0 .. 9, Thread[void]] + receiver: array[0 .. 4, Thread[void]] + + +when defined(newChan): + import std/channels + var chan = initChan[seq[string]](40) + + proc sendHandler() = + chan.send(isolate(@["Hello, Nim"])) + + proc recvHandler() = + var x: seq[string] + chan.recv(x) + discard x + +elif defined(oldChan): + var chan: Channel[seq[string]] + + chan.open(maxItems = 40) + + proc sendHandler() = + chan.send(@["Hello, Nim"]) + + + proc recvHandler() = + let x = chan.recv() + discard x + +template benchmark() = + for i in 0 .. sender.high: + createThread(sender[i], sendHandler) + + joinThreads(sender) + + + for i in 0 .. receiver.high: + createThread(receiver[i], recvHandler) + + let start = now() + joinThreads(receiver) + echo now() - start + +benchmark() +``` + +The new implementation is much faster than the old one! + + +| Implementation | Time | +| --------------------------------- | ------------------------------------ | +| system/channels + refc(-d:danger) | 433 microseconds and 590 nanoseconds | +| std/channels + orc(-d:danger) | 137 microseconds and 522 nanoseconds | + + + +## Summary + +The new channels implementation makes ORC suitable for sharing data between threads. Data races are detected at compile time. + +If you use latest Nim version, you can run the example above and experiment `std/channels` with your own programs. Please try it out and give us your feedback! + +## Further information + +- [Isolated data for Nim](https://github.com/nim-lang/RFCs/issues/244) +- [Introduction to ARC/ORC in Nim](https://nim-lang.org/blog/2020/10/15/introduction-to-arc-orc-in-nim.html) +- [ORC - Vorsprung durch Algorithmen](https://nim-lang.org/blog/2020/12/08/introducing-orc.html) + + +------- + +If you like this article and how we evolve Nim, please consider a donation. You can donate via: + +- [Open Collective](https://opencollective.com/nim) + +- [Patreon](https://www.patreon.com/araq) + +- [PayPal](https://www.paypal.com/donate/?cmd=_s-xclick&hosted_button_id=FLWX5V2PMAXAU) + +- Bitcoin: 1BXfuKM2uvoD6mbx4g5xM3eQhLzkCK77tJ + + +If you are a company, we also offer commercial support. Please get in touch with us via [support@nim-lang.org](mailto:support@nim-lang.org). As a commercial backer, you can decide what features and bugfixes should be prioritized. From 4a5901e9e8ad9d8af53e4f1f70714779d64b02ad Mon Sep 17 00:00:00 2001 From: flywind Date: Thu, 11 Mar 2021 09:16:51 +0800 Subject: [PATCH 02/37] Update 2021-03-12-new-Nim-channels.md --- jekyll/_posts/2021-03-12-new-Nim-channels.md | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/jekyll/_posts/2021-03-12-new-Nim-channels.md b/jekyll/_posts/2021-03-12-new-Nim-channels.md index 582e1f0ee..faf710af7 100644 --- a/jekyll/_posts/2021-03-12-new-Nim-channels.md +++ b/jekyll/_posts/2021-03-12-new-Nim-channels.md @@ -1,3 +1,11 @@ +--- + +title: "The new Nim channels implementation for ORC" +author: xflywind +excerpt: "The new channels library is efficient and safe to use" + +--- + # The new Nim channels implementation for ORC Version 1.4 ships with the so-called ORC memory management algorithm. ORC is the existing ARC algorithm (first shipped in version 1.2) plus a cycle collector. The Nim devel branch also introduces a new module called `std/isolation`. With it we can pass `isolated` data to threads safely and easily. It prevents data races at compile time. Recently `std/channels` is merged to the devel branch which is designed for ORC. It combines `isolated` data and `channels` and is efficient and safe to use. From f28cebd95578ad533bcf99926c4b41e2375242ca Mon Sep 17 00:00:00 2001 From: flywind Date: Thu, 11 Mar 2021 09:56:09 +0800 Subject: [PATCH 03/37] Update jekyll/_posts/2021-03-12-new-Nim-channels.md --- jekyll/_posts/2021-03-12-new-Nim-channels.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/jekyll/_posts/2021-03-12-new-Nim-channels.md b/jekyll/_posts/2021-03-12-new-Nim-channels.md index faf710af7..a685541f1 100644 --- a/jekyll/_posts/2021-03-12-new-Nim-channels.md +++ b/jekyll/_posts/2021-03-12-new-Nim-channels.md @@ -14,7 +14,7 @@ Version 1.4 ships with the so-called ORC memory management algorithm. ORC is the ## Background -A channel is a model for sharing memory via message passing. A thread is able to send or receive messages over a channel. It’s like sending a letter to your friend. The postman is the channel. Your friend is the receiver. You may know `system/channels` already exists. What’s the difference between new channels implementation and the old one? If you use the old one, you need to copy your letter by hand first and send the copied one to your friend instead. Then your friend may mark something on the copied letter, it won’t affect the original letter. It works fine, however it is not efficient. If you use the new one, you only need to put your letter in the mailbox. No need to copy your letter! +A channel is a model for sharing memory via message passing. A thread is able to send or receive messages over a channel. It's like sending a letter to your friend. The postman is the channel. Your friend is the receiver. You may know `system/channels` already exists. What’s the difference between new channels implementation and the old one? If you use the old one, you need to copy your letter by hand first and send the copied one to your friend instead. Then your friend may mark something on the copied letter, it won’t affect the original letter. It works fine, however it is not efficient. If you use the new one, you only need to put your letter in the mailbox. No need to copy your letter! ## The advantages From 4bb8b9001fa87634a6161298aeed37133af42a4b Mon Sep 17 00:00:00 2001 From: flywind Date: Thu, 11 Mar 2021 10:01:57 +0800 Subject: [PATCH 04/37] Apply suggestions from code review Co-authored-by: Timothee Cour --- jekyll/_posts/2021-03-12-new-Nim-channels.md | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/jekyll/_posts/2021-03-12-new-Nim-channels.md b/jekyll/_posts/2021-03-12-new-Nim-channels.md index a685541f1..bf697a316 100644 --- a/jekyll/_posts/2021-03-12-new-Nim-channels.md +++ b/jekyll/_posts/2021-03-12-new-Nim-channels.md @@ -19,7 +19,7 @@ A channel is a model for sharing memory via message passing. A thread is able to ## The advantages - Designed for ARC/ORC, no legacy code -- No need to `deepcopy`, just move data around +- No need to `deepCopy`, just move data around - No data races - Based on [Michael & Scott lock-based queues](https://www.cs.rochester.edu/~scott/papers/1996_PODC_queues.pdf) @@ -37,7 +37,7 @@ A channel is a model for sharing memory via message passing. A thread is able to **app.nim** -The main thread prepares tasks via reading `todo_urls.txt`. Then it sends JSON data to a channel. The crawl thread does the crawlers’ work. It receives URL data from the channel and downloads the contents using `httpclient`. +The main thread prepares tasks via reading `todo_urls.txt`. Then it sends JSON data to a channel. The crawl thread does the crawlers' work. It receives URL data from the channel and downloads the contents using `httpclient`. ```nim import std/channels @@ -48,8 +48,7 @@ var ch = initChan[JsonNode]() # we need to send JsonNode proc download(client: HttpClient, url: string) = let response = client.get(url) - echo "content: " - echo response.body[0 .. 20] # prints the results + echo "content: ", response.body[0 .. 20] # prints the results proc crawl = var client = newHttpClient() # the crawler From 4183f52e3dd537e2c444662bb8404987b7f61ea5 Mon Sep 17 00:00:00 2001 From: flywind Date: Fri, 12 Mar 2021 22:14:25 +0800 Subject: [PATCH 05/37] Update jekyll/_posts/2021-03-12-new-Nim-channels.md --- jekyll/_posts/2021-03-12-new-Nim-channels.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/jekyll/_posts/2021-03-12-new-Nim-channels.md b/jekyll/_posts/2021-03-12-new-Nim-channels.md index bf697a316..b071eead1 100644 --- a/jekyll/_posts/2021-03-12-new-Nim-channels.md +++ b/jekyll/_posts/2021-03-12-new-Nim-channels.md @@ -92,7 +92,7 @@ var chan3 = initChan[seq[string]](elements = 30) # buffered channel `send` proc takes something we want to send to the channel. The passed data is moved around, not copied. Because `chan.send(isolate(data))` is very common to use, `template send[T](c: var Chan[T]; src: T) = chan.send(isolate(src))` is provided for convenience. For example, you can use `chan.send("Hello World")` instead of `chan.send(isolate("Hello World!"))`. -There are two useful procs for a receiver: `recv` and `tryRecv`. `recv` blocks until something is sent to the channel. In contrast `tryRecv` doesn’t block. If no message exists in the channel, it just fails and returns `false`. We can write a while loop to call `tryRecv`and handle a message when available. +There are two useful procs for a receiver: `recv` and `tryRecv`. `recv` blocks until something is sent to the channel. In contrast `tryRecv` doesn't block. If no message exists in the channel, it just fails and returns `false`. We can write a while loop to call `tryRecv`and handle a message when available. ### It is safe and convenient From 5b87806bad4518596c8710415d6eb101eaa724b4 Mon Sep 17 00:00:00 2001 From: flywind Date: Fri, 12 Mar 2021 22:39:03 +0800 Subject: [PATCH 06/37] Update jekyll/_posts/2021-03-12-new-Nim-channels.md Co-authored-by: Danil Yarantsev --- jekyll/_posts/2021-03-12-new-Nim-channels.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/jekyll/_posts/2021-03-12-new-Nim-channels.md b/jekyll/_posts/2021-03-12-new-Nim-channels.md index b071eead1..0262188ce 100644 --- a/jekyll/_posts/2021-03-12-new-Nim-channels.md +++ b/jekyll/_posts/2021-03-12-new-Nim-channels.md @@ -66,7 +66,7 @@ proc prepareTasks(fileWithUrls: string): seq[Isolated[JsonNode]] = proc spawnCrawlers = var tasks = prepareTasks("todo_urls.json") - for t in mitems tasks: # we need a mutable view of the items" + for t in mitems tasks: # we need a mutable view of the items ch.send move t var thr: Thread[void] From 8b43a3ba26b64ab74f717633a04919a4eb35230f Mon Sep 17 00:00:00 2001 From: flywind Date: Fri, 12 Mar 2021 22:39:23 +0800 Subject: [PATCH 07/37] Update jekyll/_posts/2021-03-12-new-Nim-channels.md Co-authored-by: Danil Yarantsev --- jekyll/_posts/2021-03-12-new-Nim-channels.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/jekyll/_posts/2021-03-12-new-Nim-channels.md b/jekyll/_posts/2021-03-12-new-Nim-channels.md index 0262188ce..de884467f 100644 --- a/jekyll/_posts/2021-03-12-new-Nim-channels.md +++ b/jekyll/_posts/2021-03-12-new-Nim-channels.md @@ -207,7 +207,7 @@ The new implementation is much faster than the old one! The new channels implementation makes ORC suitable for sharing data between threads. Data races are detected at compile time. -If you use latest Nim version, you can run the example above and experiment `std/channels` with your own programs. Please try it out and give us your feedback! +If you use the latest devel, you can run the example above and experiment with `std/channels` in your own programs. Please try it out and give us your feedback! ## Further information From 5f11cf41d0b35932b19b5ec9f712607132d72079 Mon Sep 17 00:00:00 2001 From: flywind Date: Fri, 12 Mar 2021 22:39:42 +0800 Subject: [PATCH 08/37] Update jekyll/_posts/2021-03-12-new-Nim-channels.md Co-authored-by: Danil Yarantsev --- jekyll/_posts/2021-03-12-new-Nim-channels.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/jekyll/_posts/2021-03-12-new-Nim-channels.md b/jekyll/_posts/2021-03-12-new-Nim-channels.md index de884467f..b77235df6 100644 --- a/jekyll/_posts/2021-03-12-new-Nim-channels.md +++ b/jekyll/_posts/2021-03-12-new-Nim-channels.md @@ -37,7 +37,7 @@ A channel is a model for sharing memory via message passing. A thread is able to **app.nim** -The main thread prepares tasks via reading `todo_urls.txt`. Then it sends JSON data to a channel. The crawl thread does the crawlers' work. It receives URL data from the channel and downloads the contents using `httpclient`. +The main thread prepares tasks by reading `todo_urls.txt`. Then it sends JSON data to a channel. The crawl thread does the actual work - it receives URL data from the channel and downloads the contents using the `httpclient` module. ```nim import std/channels From 8db9837ac72dd32f2e9fcd9f1b5c9b468a8ad42d Mon Sep 17 00:00:00 2001 From: flywind Date: Fri, 12 Mar 2021 22:40:04 +0800 Subject: [PATCH 09/37] Update jekyll/_posts/2021-03-12-new-Nim-channels.md Co-authored-by: Danil Yarantsev --- jekyll/_posts/2021-03-12-new-Nim-channels.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/jekyll/_posts/2021-03-12-new-Nim-channels.md b/jekyll/_posts/2021-03-12-new-Nim-channels.md index b77235df6..828e703fd 100644 --- a/jekyll/_posts/2021-03-12-new-Nim-channels.md +++ b/jekyll/_posts/2021-03-12-new-Nim-channels.md @@ -78,7 +78,7 @@ joinThread(thr) First you need to import `std/channels`. -Then you can create a channel using `initChan`. It uses `mpmc` internally which stands for multiple producer, multiple consumer. The `elements` parameter is used to specify whether a channel is buffered or not. For unbuffered channel, the sender and the receiver block until the other side is ready. Sending data to a buffered channel blocks only when the buffer is full. Receiving data from a buffered channel blocks when the buffer is empty. +Then you can create a channel using `initChan`. It uses `mpmc` internally which stands for multiple producer, multiple consumer. The `elements` parameter is used to specify whether a channel is buffered or not. For an unbuffered channel, the sender and the receiver block until the other side is ready. Sending data to a buffered channel blocks only when the buffer is full. Receiving data from a buffered channel blocks when the buffer is empty. `initChan` is a generic proc, you can specify the types of the data you want to send or receive. From a3818737e2c93db7f1e61c96b7d6de946c7254f2 Mon Sep 17 00:00:00 2001 From: flywind Date: Fri, 12 Mar 2021 22:40:31 +0800 Subject: [PATCH 10/37] Update jekyll/_posts/2021-03-12-new-Nim-channels.md Co-authored-by: Danil Yarantsev --- jekyll/_posts/2021-03-12-new-Nim-channels.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/jekyll/_posts/2021-03-12-new-Nim-channels.md b/jekyll/_posts/2021-03-12-new-Nim-channels.md index 828e703fd..7d3f99b40 100644 --- a/jekyll/_posts/2021-03-12-new-Nim-channels.md +++ b/jekyll/_posts/2021-03-12-new-Nim-channels.md @@ -135,7 +135,7 @@ By means of `Isolated` data, the channels become safe and convenient to use. ## Benchmark -Here is a simple benchmark. We create 10 threads for sending data to the channel and 5 threads for receiving data from the channel. +Here is a simple benchmark. We create 10 threads that send data to the channel and 5 threads that receive it. ```nim # benchmark the new channel implementation with From 5f116606cd1e54a4ca25010fe19b1b83c74ca855 Mon Sep 17 00:00:00 2001 From: flywind Date: Sun, 14 Mar 2021 10:51:46 +0800 Subject: [PATCH 11/37] Update 2021-03-12-new-Nim-channels.md --- jekyll/_posts/2021-03-12-new-Nim-channels.md | 28 ++++++++++---------- 1 file changed, 14 insertions(+), 14 deletions(-) diff --git a/jekyll/_posts/2021-03-12-new-Nim-channels.md b/jekyll/_posts/2021-03-12-new-Nim-channels.md index 7d3f99b40..2b73a164d 100644 --- a/jekyll/_posts/2021-03-12-new-Nim-channels.md +++ b/jekyll/_posts/2021-03-12-new-Nim-channels.md @@ -14,7 +14,7 @@ Version 1.4 ships with the so-called ORC memory management algorithm. ORC is the ## Background -A channel is a model for sharing memory via message passing. A thread is able to send or receive messages over a channel. It's like sending a letter to your friend. The postman is the channel. Your friend is the receiver. You may know `system/channels` already exists. What’s the difference between new channels implementation and the old one? If you use the old one, you need to copy your letter by hand first and send the copied one to your friend instead. Then your friend may mark something on the copied letter, it won’t affect the original letter. It works fine, however it is not efficient. If you use the new one, you only need to put your letter in the mailbox. No need to copy your letter! +A channel is a model for sharing memory via message passing. A thread is able to send or receive messages over a channel. It's like sending a letter to your friend. The postman is the channel. Your friend is the receiver. You probably already know `system/channels`. What’s the difference between the new channels implementation and the old one? If you use the old one, you will need to copy your letter by hand first and send the copy to your friend. Then your friend may mark something on the copied letter and it won’t affect the original. This works fine, however it is not efficient. If you use the new implementation, you will only need to put your letter in the mailbox. No need to copy it! ## The advantages @@ -37,14 +37,14 @@ A channel is a model for sharing memory via message passing. A thread is able to **app.nim** -The main thread prepares tasks by reading `todo_urls.txt`. Then it sends JSON data to a channel. The crawl thread does the actual work - it receives URL data from the channel and downloads the contents using the `httpclient` module. +The main thread prepares tasks by reading `todo_urls.json`. Then it sends JSON data to a channel. The crawl thread does the actual work - it receives URL data from the channel and downloads the contents using the `httpclient` module. ```nim import std/channels import std/[httpclient, isolation, json] -var ch = initChan[JsonNode]() # we need to send JsonNode +var ch = newChannel[JsonNode]() # we need to send JsonNode proc download(client: HttpClient, url: string) = let response = client.get(url) @@ -78,31 +78,31 @@ joinThread(thr) First you need to import `std/channels`. -Then you can create a channel using `initChan`. It uses `mpmc` internally which stands for multiple producer, multiple consumer. The `elements` parameter is used to specify whether a channel is buffered or not. For an unbuffered channel, the sender and the receiver block until the other side is ready. Sending data to a buffered channel blocks only when the buffer is full. Receiving data from a buffered channel blocks when the buffer is empty. +Then you can create a channel using `newChannel` which returns a `Channel[T]`. It uses `mpmc` internally which stands for multiple producer, multiple consumer. The `elements` parameter is used to specify whether a channel is buffered or not. For an unbuffered channel, the sender and the receiver block until the other side is ready. Sending data to a buffered channel blocks only when the buffer is full. Receiving data from a buffered channel blocks when the buffer is empty. -`initChan` is a generic proc, you can specify the types of the data you want to send or receive. +`newChannel` is a generic proc, you can specify the types of the data you want to send or receive. ```nim -var chan1 = initChan[int]() +var chan1 = newChannel[int]() # or -var chan2 = initChan[string](elements = 1) # unbuffered channel +var chan2 = newChannel[string](elements = 1) # unbuffered channel # or -var chan3 = initChan[seq[string]](elements = 30) # buffered channel +var chan3 = newChannel[seq[string]](elements = 30) # buffered channel ``` -`send` proc takes something we want to send to the channel. The passed data is moved around, not copied. Because `chan.send(isolate(data))` is very common to use, `template send[T](c: var Chan[T]; src: T) = chan.send(isolate(src))` is provided for convenience. For example, you can use `chan.send("Hello World")` instead of `chan.send(isolate("Hello World!"))`. +`send` proc takes data that we want to send to the channel. The passed data is moved around, not copied. Because `chan.send(isolate(data))` is very common to use, `template send[T](c: var Chan[T]; src: T) = chan.send(isolate(src))` is provided for convenience. For example, you can use `chan.send("Hello World")` instead of `chan.send(isolate("Hello World!"))`. There are two useful procs for a receiver: `recv` and `tryRecv`. `recv` blocks until something is sent to the channel. In contrast `tryRecv` doesn't block. If no message exists in the channel, it just fails and returns `false`. We can write a while loop to call `tryRecv`and handle a message when available. ### It is safe and convenient -The Nim compiler rejects the program below at compile time. It says that `expression cannot be isolated: s`. Because s is a ref object, may be modified somewhere and is not unique. So the variable cannot be isolated. +The Nim compiler rejects the program below at compile time. It says that `expression cannot be isolated: s`. `s` is a `ref object`, it may be modified somewhere and is not unique, so the variable cannot be isolated. ```nim import std/[channels, json, isolation] -var chan = initChan[JsonNode]() +var chan = newChannel[JsonNode]() proc spawnCrawlers = var s = newJString("Hello, Nim") @@ -114,13 +114,13 @@ It is only allowed to pass a function call directly. ```nim import std/[channels, json, isolation] -var chan = initChan[JsonNode]() +var chan = newChannel[JsonNode]() proc spawnCrawlers = chan.send isolate(newJString("Hello, Nim")) ``` -`Isolated` data can only be moved, not copied. It is implemented as a library without bloating Nim's core type system. The `isolate` proc is used to create an isolated subgraph from the expression `value`. The expression `value` is checked at compile time . The `extract` proc is used to get the internal value of `Isolated` data. +`Isolated` data can only be moved, not copied. It is implemented as a library without bloating Nim's core type system. The `isolate` proc is used to create an isolated subgraph from the expression `value`. Whether the expression `value` is isolated is checked at compile time. The `extract` proc is used to get the internal value of `Isolated` data. ```nim import std/isolation @@ -153,7 +153,7 @@ var when defined(newChan): import std/channels - var chan = initChan[seq[string]](40) + var chan = newChannel[seq[string]](40) proc sendHandler() = chan.send(isolate(@["Hello, Nim"])) From 4b45bcdfa9c16afdb0d81d2aee1a3c6ae44bfc99 Mon Sep 17 00:00:00 2001 From: flywind Date: Sun, 14 Mar 2021 11:13:07 +0800 Subject: [PATCH 12/37] Update 2021-03-12-new-Nim-channels.md --- jekyll/_posts/2021-03-12-new-Nim-channels.md | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/jekyll/_posts/2021-03-12-new-Nim-channels.md b/jekyll/_posts/2021-03-12-new-Nim-channels.md index 2b73a164d..1761295ac 100644 --- a/jekyll/_posts/2021-03-12-new-Nim-channels.md +++ b/jekyll/_posts/2021-03-12-new-Nim-channels.md @@ -14,7 +14,7 @@ Version 1.4 ships with the so-called ORC memory management algorithm. ORC is the ## Background -A channel is a model for sharing memory via message passing. A thread is able to send or receive messages over a channel. It's like sending a letter to your friend. The postman is the channel. Your friend is the receiver. You probably already know `system/channels`. What’s the difference between the new channels implementation and the old one? If you use the old one, you will need to copy your letter by hand first and send the copy to your friend. Then your friend may mark something on the copied letter and it won’t affect the original. This works fine, however it is not efficient. If you use the new implementation, you will only need to put your letter in the mailbox. No need to copy it! +A channel is a model for sharing memory via message passing. A thread is able to send or receive messages over a channel. It's like sending a letter to your friend. The postman is the channel. Your friend is the receiver. You probably already know `system/channels`. What's the difference between the new channels implementation and the old one? If you use the old one, you will need to copy your letter by hand first and send the copy to your friend. Then your friend may mark something on the copied letter and it won't affect the original. This works fine, however it is not efficient. If you use the new implementation, you will only need to put your letter in the mailbox. No need to copy it! ## The advantages @@ -25,7 +25,7 @@ A channel is a model for sharing memory via message passing. A thread is able to ## Explore the new channels -**Note:** Be sure to compile your code with `--gc:orc –-threads:on`. +**Note:** Be sure to compile your code with `--gc:orc –-threads:on -d:ssl`. ### Let's crawl the web @@ -78,7 +78,7 @@ joinThread(thr) First you need to import `std/channels`. -Then you can create a channel using `newChannel` which returns a `Channel[T]`. It uses `mpmc` internally which stands for multiple producer, multiple consumer. The `elements` parameter is used to specify whether a channel is buffered or not. For an unbuffered channel, the sender and the receiver block until the other side is ready. Sending data to a buffered channel blocks only when the buffer is full. Receiving data from a buffered channel blocks when the buffer is empty. +Then you can create a channel using `newChannel` which returns a `Channel[T]`. It uses `mpmc` internally which stands for multiple producer, multiple consumer. The `elements` parameter is used to specify whether a channel is buffered or not. For an unbuffered channel, the sender and the receiver block until the other side is ready. Sending data to a buffered channel blocks only when the buffer is full. Receiving data from a buffered channel blocks when the buffer is empty. `newChannel` is a generic proc, you can specify the types of the data you want to send or receive. @@ -120,7 +120,7 @@ proc spawnCrawlers = chan.send isolate(newJString("Hello, Nim")) ``` -`Isolated` data can only be moved, not copied. It is implemented as a library without bloating Nim's core type system. The `isolate` proc is used to create an isolated subgraph from the expression `value`. Whether the expression `value` is isolated is checked at compile time. The `extract` proc is used to get the internal value of `Isolated` data. +`Isolated` data can only be moved, not copied. It is implemented as a library without bloating Nim's core type system. The `isolate` proc is used to create an isolated subgraph from the expression `value`. Whether the expression `value` is isolated is checked at compile time. The `extract` proc is used to get the internal value of `Isolated` data. ```nim import std/isolation @@ -130,7 +130,7 @@ doAssert data.extract == "string" doAssert data.extract == "" ``` -By means of `Isolated` data, the channels become safe and convenient to use. +By means of `Isolated` data, the channels become safer and more convenient to use. ## Benchmark From 426ce67159e5553d0c1a1e84bff7ae4b44c14d62 Mon Sep 17 00:00:00 2001 From: flywind Date: Sun, 14 Mar 2021 20:59:11 +0800 Subject: [PATCH 13/37] Update jekyll/_posts/2021-03-12-new-Nim-channels.md Co-authored-by: ee7 <45465154+ee7@users.noreply.github.com> --- jekyll/_posts/2021-03-12-new-Nim-channels.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/jekyll/_posts/2021-03-12-new-Nim-channels.md b/jekyll/_posts/2021-03-12-new-Nim-channels.md index 1761295ac..2a1f2adec 100644 --- a/jekyll/_posts/2021-03-12-new-Nim-channels.md +++ b/jekyll/_posts/2021-03-12-new-Nim-channels.md @@ -8,7 +8,7 @@ excerpt: "The new channels library is efficient and safe to use" # The new Nim channels implementation for ORC -Version 1.4 ships with the so-called ORC memory management algorithm. ORC is the existing ARC algorithm (first shipped in version 1.2) plus a cycle collector. The Nim devel branch also introduces a new module called `std/isolation`. With it we can pass `isolated` data to threads safely and easily. It prevents data races at compile time. Recently `std/channels` is merged to the devel branch which is designed for ORC. It combines `isolated` data and `channels` and is efficient and safe to use. +Version 1.4 ships with the so-called ORC memory management algorithm. ORC is the existing ARC algorithm (first shipped in version 1.2) plus a cycle collector. The Nim devel branch introduces a new module called `std/isolation`, which allows us to pass `isolated` data to threads safely and easily - it prevents data races at compile time. Another recent addition to the devel branch is `std/channels`, which is designed for ORC. It combines `isolated` data and `channels`, and is efficient and safe to use. **Note:** you need the Nim devel branch to compile the code below. From f9e5f947b0bffbb9c5f2451d752128702f2d9388 Mon Sep 17 00:00:00 2001 From: flywind Date: Sun, 14 Mar 2021 20:59:36 +0800 Subject: [PATCH 14/37] Update jekyll/_posts/2021-03-12-new-Nim-channels.md Co-authored-by: ee7 <45465154+ee7@users.noreply.github.com> --- jekyll/_posts/2021-03-12-new-Nim-channels.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/jekyll/_posts/2021-03-12-new-Nim-channels.md b/jekyll/_posts/2021-03-12-new-Nim-channels.md index 2a1f2adec..6725471a9 100644 --- a/jekyll/_posts/2021-03-12-new-Nim-channels.md +++ b/jekyll/_posts/2021-03-12-new-Nim-channels.md @@ -10,7 +10,7 @@ excerpt: "The new channels library is efficient and safe to use" Version 1.4 ships with the so-called ORC memory management algorithm. ORC is the existing ARC algorithm (first shipped in version 1.2) plus a cycle collector. The Nim devel branch introduces a new module called `std/isolation`, which allows us to pass `isolated` data to threads safely and easily - it prevents data races at compile time. Another recent addition to the devel branch is `std/channels`, which is designed for ORC. It combines `isolated` data and `channels`, and is efficient and safe to use. -**Note:** you need the Nim devel branch to compile the code below. +**Note:** at the time of writing, to compile the below code you need to use a development version of the Nim compiler (from 2021-03-12 or later). However, it should also work in Nim 1.6.0 or later. ## Background From 42513d957f54ddbaaff7a54ae3245eacccf30982 Mon Sep 17 00:00:00 2001 From: flywind Date: Sun, 14 Mar 2021 21:00:14 +0800 Subject: [PATCH 15/37] Update jekyll/_posts/2021-03-12-new-Nim-channels.md Co-authored-by: ee7 <45465154+ee7@users.noreply.github.com> --- jekyll/_posts/2021-03-12-new-Nim-channels.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/jekyll/_posts/2021-03-12-new-Nim-channels.md b/jekyll/_posts/2021-03-12-new-Nim-channels.md index 6725471a9..452381c8b 100644 --- a/jekyll/_posts/2021-03-12-new-Nim-channels.md +++ b/jekyll/_posts/2021-03-12-new-Nim-channels.md @@ -14,7 +14,7 @@ Version 1.4 ships with the so-called ORC memory management algorithm. ORC is the ## Background -A channel is a model for sharing memory via message passing. A thread is able to send or receive messages over a channel. It's like sending a letter to your friend. The postman is the channel. Your friend is the receiver. You probably already know `system/channels`. What's the difference between the new channels implementation and the old one? If you use the old one, you will need to copy your letter by hand first and send the copy to your friend. Then your friend may mark something on the copied letter and it won't affect the original. This works fine, however it is not efficient. If you use the new implementation, you will only need to put your letter in the mailbox. No need to copy it! +A channel is a model for sharing memory via message passing. A thread is able to send or receive messages over a channel. It's like sending a letter to your friend: the postman is the channel, and your friend is the receiver. You might already know `system/channels`, which is the old channels implementation. What's better in the new implementation? With the old one, first you need to copy your letter and send the copy to your friend. Then your friend may mark something on the copied letter and it won't affect the original. This works fine, but it is not efficient. If you use the new implementation, you only need to put your letter in the mailbox. No need to copy it! ## The advantages From c124ead01c28740e632ec2e8978b1b9ce99f6878 Mon Sep 17 00:00:00 2001 From: flywind Date: Sun, 14 Mar 2021 21:00:36 +0800 Subject: [PATCH 16/37] Update jekyll/_posts/2021-03-12-new-Nim-channels.md Co-authored-by: ee7 <45465154+ee7@users.noreply.github.com> --- jekyll/_posts/2021-03-12-new-Nim-channels.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/jekyll/_posts/2021-03-12-new-Nim-channels.md b/jekyll/_posts/2021-03-12-new-Nim-channels.md index 452381c8b..26031a85b 100644 --- a/jekyll/_posts/2021-03-12-new-Nim-channels.md +++ b/jekyll/_posts/2021-03-12-new-Nim-channels.md @@ -37,7 +37,7 @@ A channel is a model for sharing memory via message passing. A thread is able to **app.nim** -The main thread prepares tasks by reading `todo_urls.json`. Then it sends JSON data to a channel. The crawl thread does the actual work - it receives URL data from the channel and downloads the contents using the `httpclient` module. +The main thread prepares tasks by reading `todo_urls.json`, and then it sends JSON data to a channel. The crawl thread does the actual work - it receives URL data from the channel and downloads the contents using the `httpclient` module. ```nim import std/channels From 9d6db08bba8e52e2be3d166139ec9eace020445a Mon Sep 17 00:00:00 2001 From: flywind Date: Sun, 14 Mar 2021 21:00:53 +0800 Subject: [PATCH 17/37] Update jekyll/_posts/2021-03-12-new-Nim-channels.md Co-authored-by: ee7 <45465154+ee7@users.noreply.github.com> --- jekyll/_posts/2021-03-12-new-Nim-channels.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/jekyll/_posts/2021-03-12-new-Nim-channels.md b/jekyll/_posts/2021-03-12-new-Nim-channels.md index 26031a85b..89f7717f0 100644 --- a/jekyll/_posts/2021-03-12-new-Nim-channels.md +++ b/jekyll/_posts/2021-03-12-new-Nim-channels.md @@ -76,7 +76,7 @@ spawnCrawlers() joinThread(thr) ``` -First you need to import `std/channels`. +First, we import `std/channels`. Then you can create a channel using `newChannel` which returns a `Channel[T]`. It uses `mpmc` internally which stands for multiple producer, multiple consumer. The `elements` parameter is used to specify whether a channel is buffered or not. For an unbuffered channel, the sender and the receiver block until the other side is ready. Sending data to a buffered channel blocks only when the buffer is full. Receiving data from a buffered channel blocks when the buffer is empty. From 01c6bf0bc366d8722f1dbb75a19853415c16bcea Mon Sep 17 00:00:00 2001 From: flywind Date: Sun, 14 Mar 2021 21:03:36 +0800 Subject: [PATCH 18/37] Apply suggestions from code review Co-authored-by: ee7 <45465154+ee7@users.noreply.github.com> --- jekyll/_posts/2021-03-12-new-Nim-channels.md | 34 ++++++++------------ 1 file changed, 14 insertions(+), 20 deletions(-) diff --git a/jekyll/_posts/2021-03-12-new-Nim-channels.md b/jekyll/_posts/2021-03-12-new-Nim-channels.md index 89f7717f0..cedb09ce9 100644 --- a/jekyll/_posts/2021-03-12-new-Nim-channels.md +++ b/jekyll/_posts/2021-03-12-new-Nim-channels.md @@ -78,9 +78,9 @@ joinThread(thr) First, we import `std/channels`. -Then you can create a channel using `newChannel` which returns a `Channel[T]`. It uses `mpmc` internally which stands for multiple producer, multiple consumer. The `elements` parameter is used to specify whether a channel is buffered or not. For an unbuffered channel, the sender and the receiver block until the other side is ready. Sending data to a buffered channel blocks only when the buffer is full. Receiving data from a buffered channel blocks when the buffer is empty. +Then we can create a channel using `newChannel`, which returns a `Channel[T]`. It uses `mpmc` internally, which stands for "multiple producer, multiple consumer". The `elements` parameter is used to specify whether a channel is buffered or not. For an unbuffered channel, the sender and the receiver block until the other side is ready. Sending data to a buffered channel blocks only when the buffer is full. Receiving data from a buffered channel blocks when the buffer is empty. -`newChannel` is a generic proc, you can specify the types of the data you want to send or receive. +`newChannel` is a generic proc - you can specify the types of the data you want to send or receive. ```nim var chan1 = newChannel[int]() @@ -90,13 +90,13 @@ var chan2 = newChannel[string](elements = 1) # unbuffered channel var chan3 = newChannel[seq[string]](elements = 30) # buffered channel ``` -`send` proc takes data that we want to send to the channel. The passed data is moved around, not copied. Because `chan.send(isolate(data))` is very common to use, `template send[T](c: var Chan[T]; src: T) = chan.send(isolate(src))` is provided for convenience. For example, you can use `chan.send("Hello World")` instead of `chan.send(isolate("Hello World!"))`. +The `send` proc takes data that we want to send to the channel. The passed data is moved around, not copied. Because `chan.send(isolate(data))` is very common to use, `template send[T](c: var Chan[T]; src: T) = chan.send(isolate(src))` is provided for convenience. For example, you can use `chan.send("Hello World")` instead of `chan.send(isolate("Hello World!"))`. -There are two useful procs for a receiver: `recv` and `tryRecv`. `recv` blocks until something is sent to the channel. In contrast `tryRecv` doesn't block. If no message exists in the channel, it just fails and returns `false`. We can write a while loop to call `tryRecv`and handle a message when available. +There are two useful procs for a receiver: `recv` and `tryRecv`. `recv` blocks until something is sent to the channel. In contrast, `tryRecv` doesn't block - if no message exists in the channel, it just fails and returns `false`. We can write a while loop to call `tryRecv`and handle a message when available. -### It is safe and convenient +### It is safe and convenient -The Nim compiler rejects the program below at compile time. It says that `expression cannot be isolated: s`. `s` is a `ref object`, it may be modified somewhere and is not unique, so the variable cannot be isolated. +The Nim compiler rejects the program below at compile time. It says that `expression cannot be isolated: s`. This is because `s` is a `ref object` - it may be modified somewhere and is not unique, so the variable cannot be isolated. ```nim @@ -104,7 +104,7 @@ import std/[channels, json, isolation] var chan = newChannel[JsonNode]() -proc spawnCrawlers = +proc spawnCrawlers = var s = newJString("Hello, Nim") chan.send isolate(s) ``` @@ -116,7 +116,7 @@ import std/[channels, json, isolation] var chan = newChannel[JsonNode]() -proc spawnCrawlers = +proc spawnCrawlers = chan.send isolate(newJString("Hello, Nim")) ``` @@ -144,15 +144,13 @@ Here is a simple benchmark. We create 10 threads that send data to the channel a # benchmark the old channel implementation with # `nim c -r --threads:on -d:oldChan -d:danger app.nim` -import std/[os, times, isolation] +import std/times var sender: array[0 .. 9, Thread[void]] receiver: array[0 .. 4, Thread[void]] - - when defined(newChan): - import std/channels + import std/[channels, isolation] var chan = newChannel[seq[string]](40) proc sendHandler() = @@ -170,8 +168,6 @@ elif defined(oldChan): proc sendHandler() = chan.send(@["Hello, Nim"]) - - proc recvHandler() = let x = chan.recv() discard x @@ -181,8 +177,6 @@ template benchmark() = createThread(sender[i], sendHandler) joinThreads(sender) - - for i in 0 .. receiver.high: createThread(receiver[i], recvHandler) @@ -196,10 +190,10 @@ benchmark() The new implementation is much faster than the old one! -| Implementation | Time | -| --------------------------------- | ------------------------------------ | -| system/channels + refc(-d:danger) | 433 microseconds and 590 nanoseconds | -| std/channels + orc(-d:danger) | 137 microseconds and 522 nanoseconds | +| Implementation | Elapsed time | +| ---------------------------------- | -----------: | +| system/channels + refc (-d:danger) | 433 μs | +| std/channels + orc (-d:danger) | 137 μs | From 965e39284ecda50893423acaaac1a9eb89797238 Mon Sep 17 00:00:00 2001 From: flywind Date: Sun, 14 Mar 2021 21:03:49 +0800 Subject: [PATCH 19/37] Update jekyll/_posts/2021-03-12-new-Nim-channels.md Co-authored-by: ee7 <45465154+ee7@users.noreply.github.com> --- jekyll/_posts/2021-03-12-new-Nim-channels.md | 2 -- 1 file changed, 2 deletions(-) diff --git a/jekyll/_posts/2021-03-12-new-Nim-channels.md b/jekyll/_posts/2021-03-12-new-Nim-channels.md index cedb09ce9..b53d43690 100644 --- a/jekyll/_posts/2021-03-12-new-Nim-channels.md +++ b/jekyll/_posts/2021-03-12-new-Nim-channels.md @@ -42,8 +42,6 @@ The main thread prepares tasks by reading `todo_urls.json`, and then it sends JS ```nim import std/channels import std/[httpclient, isolation, json] - - var ch = newChannel[JsonNode]() # we need to send JsonNode proc download(client: HttpClient, url: string) = From fe2b67e518ac1da03fb0fb3f10bdb159ff9fc6f7 Mon Sep 17 00:00:00 2001 From: flywind Date: Sun, 14 Mar 2021 21:04:13 +0800 Subject: [PATCH 20/37] Apply suggestions from code review Co-authored-by: ee7 <45465154+ee7@users.noreply.github.com> --- jekyll/_posts/2021-03-12-new-Nim-channels.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/jekyll/_posts/2021-03-12-new-Nim-channels.md b/jekyll/_posts/2021-03-12-new-Nim-channels.md index b53d43690..f040415d3 100644 --- a/jekyll/_posts/2021-03-12-new-Nim-channels.md +++ b/jekyll/_posts/2021-03-12-new-Nim-channels.md @@ -62,7 +62,7 @@ proc prepareTasks(fileWithUrls: string): seq[Isolated[JsonNode]] = for line in lines(fileWithUrls): result.add isolate(parseJson(line)) # parse JSON file -proc spawnCrawlers = +proc spawnCrawlers = var tasks = prepareTasks("todo_urls.json") for t in mitems tasks: # we need a mutable view of the items ch.send move t From accb6971f2bfd98b94a6b4550a7b5de71ff6a559 Mon Sep 17 00:00:00 2001 From: flywind Date: Mon, 15 Mar 2021 09:14:26 +0800 Subject: [PATCH 21/37] Update jekyll/_posts/2021-03-12-new-Nim-channels.md Co-authored-by: ee7 <45465154+ee7@users.noreply.github.com> --- jekyll/_posts/2021-03-12-new-Nim-channels.md | 1 + 1 file changed, 1 insertion(+) diff --git a/jekyll/_posts/2021-03-12-new-Nim-channels.md b/jekyll/_posts/2021-03-12-new-Nim-channels.md index f040415d3..8300a2c75 100644 --- a/jekyll/_posts/2021-03-12-new-Nim-channels.md +++ b/jekyll/_posts/2021-03-12-new-Nim-channels.md @@ -42,6 +42,7 @@ The main thread prepares tasks by reading `todo_urls.json`, and then it sends JS ```nim import std/channels import std/[httpclient, isolation, json] + var ch = newChannel[JsonNode]() # we need to send JsonNode proc download(client: HttpClient, url: string) = From 84c5b0e5c9f7b94270299be5d61067aea72c425c Mon Sep 17 00:00:00 2001 From: flywind Date: Mon, 15 Mar 2021 11:22:48 +0800 Subject: [PATCH 22/37] Update jekyll/_posts/2021-03-12-new-Nim-channels.md Co-authored-by: Timothee Cour --- jekyll/_posts/2021-03-12-new-Nim-channels.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/jekyll/_posts/2021-03-12-new-Nim-channels.md b/jekyll/_posts/2021-03-12-new-Nim-channels.md index 8300a2c75..65ae62594 100644 --- a/jekyll/_posts/2021-03-12-new-Nim-channels.md +++ b/jekyll/_posts/2021-03-12-new-Nim-channels.md @@ -198,7 +198,7 @@ The new implementation is much faster than the old one! ## Summary -The new channels implementation makes ORC suitable for sharing data between threads. Data races are detected at compile time. +The new channels implementation makes ORC suitable for sharing data between threads. Data races are prevented at compile time by sending isolated subgraphs checked at compile time. If you use the latest devel, you can run the example above and experiment with `std/channels` in your own programs. Please try it out and give us your feedback! From ef79a5db1e11b2634eb593a53b9fc545739a09cc Mon Sep 17 00:00:00 2001 From: flywind Date: Mon, 15 Mar 2021 11:22:59 +0800 Subject: [PATCH 23/37] Update jekyll/_posts/2021-03-12-new-Nim-channels.md Co-authored-by: Timothee Cour --- jekyll/_posts/2021-03-12-new-Nim-channels.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/jekyll/_posts/2021-03-12-new-Nim-channels.md b/jekyll/_posts/2021-03-12-new-Nim-channels.md index 65ae62594..fd36d8c78 100644 --- a/jekyll/_posts/2021-03-12-new-Nim-channels.md +++ b/jekyll/_posts/2021-03-12-new-Nim-channels.md @@ -146,8 +146,8 @@ Here is a simple benchmark. We create 10 threads that send data to the channel a import std/times var - sender: array[0 .. 9, Thread[void]] - receiver: array[0 .. 4, Thread[void]] + sender: array[10, Thread[void]] + receiver: array[5, Thread[void]] when defined(newChan): import std/[channels, isolation] var chan = newChannel[seq[string]](40) From d2383bde5c29d00df205d9f734d649cd6c8a9a76 Mon Sep 17 00:00:00 2001 From: flywind Date: Mon, 15 Mar 2021 11:25:01 +0800 Subject: [PATCH 24/37] Apply suggestions from code review Co-authored-by: Timothee Cour Co-authored-by: ee7 <45465154+ee7@users.noreply.github.com> --- jekyll/_posts/2021-03-12-new-Nim-channels.md | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/jekyll/_posts/2021-03-12-new-Nim-channels.md b/jekyll/_posts/2021-03-12-new-Nim-channels.md index fd36d8c78..e8fbb0cfb 100644 --- a/jekyll/_posts/2021-03-12-new-Nim-channels.md +++ b/jekyll/_posts/2021-03-12-new-Nim-channels.md @@ -138,10 +138,10 @@ Here is a simple benchmark. We create 10 threads that send data to the channel a ```nim # benchmark the new channel implementation with -# `nim c -r --threads:on --gc:orc -d:newChan -d:danger app.nim` +# `nim r --threads:on --gc:orc -d:newChan -d:danger app.nim` # # benchmark the old channel implementation with -# `nim c -r --threads:on -d:oldChan -d:danger app.nim` +# `nim r --threads:on -d:oldChan -d:danger app.nim` import std/times @@ -167,13 +167,14 @@ elif defined(oldChan): proc sendHandler() = chan.send(@["Hello, Nim"]) + proc recvHandler() = let x = chan.recv() discard x template benchmark() = - for i in 0 .. sender.high: - createThread(sender[i], sendHandler) + for t in mitems(sender): + t.createThread(sendHandler) joinThreads(sender) for i in 0 .. receiver.high: From df832467a752f18adb1c46913eadd0a8ef41e897 Mon Sep 17 00:00:00 2001 From: flywind Date: Mon, 15 Mar 2021 11:38:00 +0800 Subject: [PATCH 25/37] Update jekyll/_posts/2021-03-12-new-Nim-channels.md --- jekyll/_posts/2021-03-12-new-Nim-channels.md | 1 - 1 file changed, 1 deletion(-) diff --git a/jekyll/_posts/2021-03-12-new-Nim-channels.md b/jekyll/_posts/2021-03-12-new-Nim-channels.md index e8fbb0cfb..d7c4d929d 100644 --- a/jekyll/_posts/2021-03-12-new-Nim-channels.md +++ b/jekyll/_posts/2021-03-12-new-Nim-channels.md @@ -21,7 +21,6 @@ A channel is a model for sharing memory via message passing. A thread is able to - Designed for ARC/ORC, no legacy code - No need to `deepCopy`, just move data around - No data races -- Based on [Michael & Scott lock-based queues](https://www.cs.rochester.edu/~scott/papers/1996_PODC_queues.pdf) ## Explore the new channels From 40b3d706b7df2d6411b911fa06486001bad4ce89 Mon Sep 17 00:00:00 2001 From: flywind Date: Mon, 15 Mar 2021 14:13:41 +0800 Subject: [PATCH 26/37] Update 2021-03-12-new-Nim-channels.md --- jekyll/_posts/2021-03-12-new-Nim-channels.md | 24 +++++++++----------- 1 file changed, 11 insertions(+), 13 deletions(-) diff --git a/jekyll/_posts/2021-03-12-new-Nim-channels.md b/jekyll/_posts/2021-03-12-new-Nim-channels.md index d7c4d929d..3a3d89cd2 100644 --- a/jekyll/_posts/2021-03-12-new-Nim-channels.md +++ b/jekyll/_posts/2021-03-12-new-Nim-channels.md @@ -14,7 +14,7 @@ Version 1.4 ships with the so-called ORC memory management algorithm. ORC is the ## Background -A channel is a model for sharing memory via message passing. A thread is able to send or receive messages over a channel. It's like sending a letter to your friend: the postman is the channel, and your friend is the receiver. You might already know `system/channels`, which is the old channels implementation. What's better in the new implementation? With the old one, first you need to copy your letter and send the copy to your friend. Then your friend may mark something on the copied letter and it won't affect the original. This works fine, but it is not efficient. If you use the new implementation, you only need to put your letter in the mailbox. No need to copy it! +A channel is a model for sharing memory via message passing. A thread is able to send or receive messages over a channel. It's like sending a letter to your friend: the postman is the channel, and your friend is the receiver. You might already know `system/channels_builtin`, which is the old channels implementation. What's better in the new implementation? With the old one, first you need to copy your letter and send the copy to your friend. Then your friend may mark something on the copied letter and it won't affect the original. This works fine, but it is not efficient. If you use the new implementation, you only need to put your letter in the mailbox. No need to copy it! ## The advantages @@ -50,12 +50,12 @@ proc download(client: HttpClient, url: string) = proc crawl = var client = newHttpClient() # the crawler + defer: client.close() var data: JsonNode ch.recv(data) # the JSON data if data != nil: for url in data["url"]: download(client, url.getStr) - client.close() proc prepareTasks(fileWithUrls: string): seq[Isolated[JsonNode]] = result = @[] @@ -104,7 +104,8 @@ var chan = newChannel[JsonNode]() proc spawnCrawlers = var s = newJString("Hello, Nim") - chan.send isolate(s) + chan.send isolate(s) # compile time error + chan.send unsafeIsolate(s) # ok: user's responsability to check that `s` isn't mutated ``` It is only allowed to pass a function call directly. @@ -151,9 +152,6 @@ when defined(newChan): import std/[channels, isolation] var chan = newChannel[seq[string]](40) - proc sendHandler() = - chan.send(isolate(@["Hello, Nim"])) - proc recvHandler() = var x: seq[string] chan.recv(x) @@ -164,13 +162,13 @@ elif defined(oldChan): chan.open(maxItems = 40) - proc sendHandler() = - chan.send(@["Hello, Nim"]) - proc recvHandler() = let x = chan.recv() discard x +proc sendHandler() = + chan.send(@["Hello, Nim"]) + template benchmark() = for t in mitems(sender): t.createThread(sendHandler) @@ -189,10 +187,10 @@ benchmark() The new implementation is much faster than the old one! -| Implementation | Elapsed time | -| ---------------------------------- | -----------: | -| system/channels + refc (-d:danger) | 433 μs | -| std/channels + orc (-d:danger) | 137 μs | +| Implementation | Elapsed time | +| ------------------------------------------ | -----------: | +| system/channels_builtin + refc (-d:danger) | 433 μs | +| std/channels + orc (-d:danger) | 137 μs | From 52f3a0726e5b6b857564933e118f3325e54cce0e Mon Sep 17 00:00:00 2001 From: flywind Date: Mon, 15 Mar 2021 14:46:25 +0800 Subject: [PATCH 27/37] update benchmark --- jekyll/_posts/2021-03-12-new-Nim-channels.md | 17 ++++++++++------- 1 file changed, 10 insertions(+), 7 deletions(-) diff --git a/jekyll/_posts/2021-03-12-new-Nim-channels.md b/jekyll/_posts/2021-03-12-new-Nim-channels.md index 3a3d89cd2..8c209d3eb 100644 --- a/jekyll/_posts/2021-03-12-new-Nim-channels.md +++ b/jekyll/_posts/2021-03-12-new-Nim-channels.md @@ -134,7 +134,7 @@ By means of `Isolated` data, the channels become safer and more convenient to us ## Benchmark -Here is a simple benchmark. We create 10 threads that send data to the channel and 5 threads that receive it. +Here is a simple benchmark. We create 40 threads that send data to the channel and 5/10/20 threads that receive it. ```nim # benchmark the new channel implementation with @@ -146,8 +146,11 @@ Here is a simple benchmark. We create 10 threads that send data to the channel a import std/times var - sender: array[10, Thread[void]] - receiver: array[5, Thread[void]] + sender: array[40, Thread[void]] + receiver: array[5, Thread[void]] + # receiver: array[10, Thread[void]] # with 10 threads + # receiver: array[20, Thread[void]] # with 20 threads + when defined(newChan): import std/[channels, isolation] var chan = newChannel[seq[string]](40) @@ -187,10 +190,10 @@ benchmark() The new implementation is much faster than the old one! -| Implementation | Elapsed time | -| ------------------------------------------ | -----------: | -| system/channels_builtin + refc (-d:danger) | 433 μs | -| std/channels + orc (-d:danger) | 137 μs | +| Implementation | 5 threads | 10 threads | 20 threads | +| ------------------------------------------ | -----------: | -----------: | ----------: | +| system/channels_builtin + refc (-d:danger) | 458 μs | 859 μs | 1710 μs | +| std/channels + orc (-d:danger) | 188 μs | 258 μs | 428 μs | From 5c9b9ec5e8b89414df28497ad302135a63933cae Mon Sep 17 00:00:00 2001 From: flywind Date: Wed, 17 Mar 2021 23:04:19 +0800 Subject: [PATCH 28/37] Apply suggestions from code review --- jekyll/_posts/2021-03-12-new-Nim-channels.md | 28 +++----------------- 1 file changed, 4 insertions(+), 24 deletions(-) diff --git a/jekyll/_posts/2021-03-12-new-Nim-channels.md b/jekyll/_posts/2021-03-12-new-Nim-channels.md index 8c209d3eb..5f7ba4692 100644 --- a/jekyll/_posts/2021-03-12-new-Nim-channels.md +++ b/jekyll/_posts/2021-03-12-new-Nim-channels.md @@ -41,35 +41,27 @@ The main thread prepares tasks by reading `todo_urls.json`, and then it sends JS ```nim import std/channels import std/[httpclient, isolation, json] - var ch = newChannel[JsonNode]() # we need to send JsonNode - proc download(client: HttpClient, url: string) = let response = client.get(url) echo "content: ", response.body[0 .. 20] # prints the results - proc crawl = var client = newHttpClient() # the crawler defer: client.close() - var data: JsonNode - ch.recv(data) # the JSON data + let data = ch.recv() # the JSON data if data != nil: for url in data["url"]: download(client, url.getStr) - proc prepareTasks(fileWithUrls: string): seq[Isolated[JsonNode]] = result = @[] for line in lines(fileWithUrls): result.add isolate(parseJson(line)) # parse JSON file - proc spawnCrawlers = var tasks = prepareTasks("todo_urls.json") for t in mitems tasks: # we need a mutable view of the items ch.send move t - var thr: Thread[void] createThread(thr, crawl) # create crawl thread - spawnCrawlers() joinThread(thr) ``` @@ -150,40 +142,28 @@ var receiver: array[5, Thread[void]] # receiver: array[10, Thread[void]] # with 10 threads # receiver: array[20, Thread[void]] # with 20 threads - when defined(newChan): import std/[channels, isolation] var chan = newChannel[seq[string]](40) - - proc recvHandler() = - var x: seq[string] - chan.recv(x) - discard x - elif defined(oldChan): var chan: Channel[seq[string]] - chan.open(maxItems = 40) - proc recvHandler() = - let x = chan.recv() - discard x +proc recvHandler() = + let x = chan.recv() + discard x proc sendHandler() = chan.send(@["Hello, Nim"]) - template benchmark() = for t in mitems(sender): t.createThread(sendHandler) - joinThreads(sender) for i in 0 .. receiver.high: createThread(receiver[i], recvHandler) - let start = now() joinThreads(receiver) echo now() - start - benchmark() ``` From 44abd9ac33990814eeb3a3b3ba267a58571d2b86 Mon Sep 17 00:00:00 2001 From: flywind Date: Thu, 18 Mar 2021 10:20:25 +0800 Subject: [PATCH 29/37] Update jekyll/_posts/2021-03-12-new-Nim-channels.md Co-authored-by: konsumlamm <44230978+konsumlamm@users.noreply.github.com> --- jekyll/_posts/2021-03-12-new-Nim-channels.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/jekyll/_posts/2021-03-12-new-Nim-channels.md b/jekyll/_posts/2021-03-12-new-Nim-channels.md index 5f7ba4692..a120075ba 100644 --- a/jekyll/_posts/2021-03-12-new-Nim-channels.md +++ b/jekyll/_posts/2021-03-12-new-Nim-channels.md @@ -8,7 +8,7 @@ excerpt: "The new channels library is efficient and safe to use" # The new Nim channels implementation for ORC -Version 1.4 ships with the so-called ORC memory management algorithm. ORC is the existing ARC algorithm (first shipped in version 1.2) plus a cycle collector. The Nim devel branch introduces a new module called `std/isolation`, which allows us to pass `isolated` data to threads safely and easily - it prevents data races at compile time. Another recent addition to the devel branch is `std/channels`, which is designed for ORC. It combines `isolated` data and `channels`, and is efficient and safe to use. +Version 1.4 ships with the so-called ORC memory management algorithm. ORC is the existing ARC algorithm (first shipped in version 1.2) plus a cycle collector. The Nim devel branch introduces a new module called `std/isolation`, which allows us to pass `Isolated` data to threads safely and easily - it prevents data races at compile time. Another recent addition to the devel branch is `std/channels`, which is designed for ORC. It combines `Isolated` data and `channels`, and is efficient and safe to use. **Note:** at the time of writing, to compile the below code you need to use a development version of the Nim compiler (from 2021-03-12 or later). However, it should also work in Nim 1.6.0 or later. From 0d6700abe7d600909776c3c57ad37967d439527c Mon Sep 17 00:00:00 2001 From: flywind Date: Thu, 18 Mar 2021 10:20:41 +0800 Subject: [PATCH 30/37] Update jekyll/_posts/2021-03-12-new-Nim-channels.md Co-authored-by: konsumlamm <44230978+konsumlamm@users.noreply.github.com> --- jekyll/_posts/2021-03-12-new-Nim-channels.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/jekyll/_posts/2021-03-12-new-Nim-channels.md b/jekyll/_posts/2021-03-12-new-Nim-channels.md index a120075ba..47b8cff0f 100644 --- a/jekyll/_posts/2021-03-12-new-Nim-channels.md +++ b/jekyll/_posts/2021-03-12-new-Nim-channels.md @@ -10,7 +10,7 @@ excerpt: "The new channels library is efficient and safe to use" Version 1.4 ships with the so-called ORC memory management algorithm. ORC is the existing ARC algorithm (first shipped in version 1.2) plus a cycle collector. The Nim devel branch introduces a new module called `std/isolation`, which allows us to pass `Isolated` data to threads safely and easily - it prevents data races at compile time. Another recent addition to the devel branch is `std/channels`, which is designed for ORC. It combines `Isolated` data and `channels`, and is efficient and safe to use. -**Note:** at the time of writing, to compile the below code you need to use a development version of the Nim compiler (from 2021-03-12 or later). However, it should also work in Nim 1.6.0 or later. +**Note:** At the time of writing, to compile the below code you need to use a development version of the Nim compiler (from 2021-03-12 or later). However, it should also work in Nim 1.6.0 or later. ## Background From bc1bbb6843b206a60a645eac0d53918f53acd741 Mon Sep 17 00:00:00 2001 From: flywind Date: Thu, 18 Mar 2021 10:21:53 +0800 Subject: [PATCH 31/37] Update jekyll/_posts/2021-03-12-new-Nim-channels.md Co-authored-by: konsumlamm <44230978+konsumlamm@users.noreply.github.com> --- jekyll/_posts/2021-03-12-new-Nim-channels.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/jekyll/_posts/2021-03-12-new-Nim-channels.md b/jekyll/_posts/2021-03-12-new-Nim-channels.md index 47b8cff0f..e121f40eb 100644 --- a/jekyll/_posts/2021-03-12-new-Nim-channels.md +++ b/jekyll/_posts/2021-03-12-new-Nim-channels.md @@ -68,7 +68,7 @@ joinThread(thr) First, we import `std/channels`. -Then we can create a channel using `newChannel`, which returns a `Channel[T]`. It uses `mpmc` internally, which stands for "multiple producer, multiple consumer". The `elements` parameter is used to specify whether a channel is buffered or not. For an unbuffered channel, the sender and the receiver block until the other side is ready. Sending data to a buffered channel blocks only when the buffer is full. Receiving data from a buffered channel blocks when the buffer is empty. +Then we can create a channel using `newChannel`, which returns a `Channel[T]`. It uses MPMC internally, which stands for "multiple producer, multiple consumer". The `elements` parameter is used to specify whether a channel is buffered or not. For an unbuffered channel, the sender and the receiver block until the other side is ready. Sending data to a buffered channel blocks only when the buffer is full. Receiving data from a buffered channel blocks when the buffer is empty. `newChannel` is a generic proc - you can specify the types of the data you want to send or receive. From 3d1169d664c6f5dc600651fc82c32fe353e4769d Mon Sep 17 00:00:00 2001 From: flywind Date: Thu, 18 Mar 2021 10:22:07 +0800 Subject: [PATCH 32/37] Update jekyll/_posts/2021-03-12-new-Nim-channels.md Co-authored-by: konsumlamm <44230978+konsumlamm@users.noreply.github.com> --- jekyll/_posts/2021-03-12-new-Nim-channels.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/jekyll/_posts/2021-03-12-new-Nim-channels.md b/jekyll/_posts/2021-03-12-new-Nim-channels.md index e121f40eb..6201f6474 100644 --- a/jekyll/_posts/2021-03-12-new-Nim-channels.md +++ b/jekyll/_posts/2021-03-12-new-Nim-channels.md @@ -82,7 +82,7 @@ var chan3 = newChannel[seq[string]](elements = 30) # buffered channel The `send` proc takes data that we want to send to the channel. The passed data is moved around, not copied. Because `chan.send(isolate(data))` is very common to use, `template send[T](c: var Chan[T]; src: T) = chan.send(isolate(src))` is provided for convenience. For example, you can use `chan.send("Hello World")` instead of `chan.send(isolate("Hello World!"))`. -There are two useful procs for a receiver: `recv` and `tryRecv`. `recv` blocks until something is sent to the channel. In contrast, `tryRecv` doesn't block - if no message exists in the channel, it just fails and returns `false`. We can write a while loop to call `tryRecv`and handle a message when available. +There are two useful procs for a receiver: `recv` and `tryRecv`. `recv` blocks until something is sent to the channel. In contrast, `tryRecv` doesn't block - if no message exists in the channel, it just fails and returns `false`. We can write a while loop to call `tryRecv` and handle a message when available. ### It is safe and convenient From 1aa803fbe0934f116af5f05f8543c785ac7addc9 Mon Sep 17 00:00:00 2001 From: flywind Date: Thu, 18 Mar 2021 10:23:02 +0800 Subject: [PATCH 33/37] Apply suggestions from code review Co-authored-by: konsumlamm <44230978+konsumlamm@users.noreply.github.com> --- jekyll/_posts/2021-03-12-new-Nim-channels.md | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/jekyll/_posts/2021-03-12-new-Nim-channels.md b/jekyll/_posts/2021-03-12-new-Nim-channels.md index 6201f6474..dc42c17c9 100644 --- a/jekyll/_posts/2021-03-12-new-Nim-channels.md +++ b/jekyll/_posts/2021-03-12-new-Nim-channels.md @@ -80,13 +80,13 @@ var chan2 = newChannel[string](elements = 1) # unbuffered channel var chan3 = newChannel[seq[string]](elements = 30) # buffered channel ``` -The `send` proc takes data that we want to send to the channel. The passed data is moved around, not copied. Because `chan.send(isolate(data))` is very common to use, `template send[T](c: var Chan[T]; src: T) = chan.send(isolate(src))` is provided for convenience. For example, you can use `chan.send("Hello World")` instead of `chan.send(isolate("Hello World!"))`. +The `send` proc takes data that we want to send to the channel. The passed data is moved around, not copied. Because `chan.send(isolate(data))` is very common to use, `template send[T](c: var Channel[T]; src: T) = c.send(isolate(src))` is provided for convenience. For example, you can use `chan.send("Hello world")` instead of `chan.send(isolate("Hello world!"))`. There are two useful procs for a receiver: `recv` and `tryRecv`. `recv` blocks until something is sent to the channel. In contrast, `tryRecv` doesn't block - if no message exists in the channel, it just fails and returns `false`. We can write a while loop to call `tryRecv` and handle a message when available. ### It is safe and convenient -The Nim compiler rejects the program below at compile time. It says that `expression cannot be isolated: s`. This is because `s` is a `ref object` - it may be modified somewhere and is not unique, so the variable cannot be isolated. +The Nim compiler rejects the program below. It says that `expression cannot be isolated: s`. This is because `s` is a `ref object` - it may be modified somewhere and is not unique, so the variable cannot be isolated. ```nim @@ -118,7 +118,7 @@ import std/isolation var data = isolate("string") doAssert data.extract == "string" -doAssert data.extract == "" +doAssert data.extract == "" # the value was moved ``` By means of `Isolated` data, the channels become safer and more convenient to use. @@ -126,7 +126,7 @@ By means of `Isolated` data, the channels become safer and more convenient to us ## Benchmark -Here is a simple benchmark. We create 40 threads that send data to the channel and 5/10/20 threads that receive it. +Here is a simple benchmark. We create 40 threads that send data to the channel and 5/10/20 threads that receive from it. ```nim # benchmark the new channel implementation with @@ -155,6 +155,7 @@ proc recvHandler() = proc sendHandler() = chan.send(@["Hello, Nim"]) + template benchmark() = for t in mitems(sender): t.createThread(sendHandler) @@ -164,6 +165,7 @@ template benchmark() = let start = now() joinThreads(receiver) echo now() - start + benchmark() ``` From f2a595e0fdd98659de0259c66e7e85ba85f37872 Mon Sep 17 00:00:00 2001 From: flywind Date: Tue, 23 Mar 2021 22:01:28 +0800 Subject: [PATCH 34/37] Update jekyll/_posts/2021-03-12-new-Nim-channels.md Co-authored-by: Zoom --- jekyll/_posts/2021-03-12-new-Nim-channels.md | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/jekyll/_posts/2021-03-12-new-Nim-channels.md b/jekyll/_posts/2021-03-12-new-Nim-channels.md index dc42c17c9..15309ff8e 100644 --- a/jekyll/_posts/2021-03-12-new-Nim-channels.md +++ b/jekyll/_posts/2021-03-12-new-Nim-channels.md @@ -68,9 +68,13 @@ joinThread(thr) First, we import `std/channels`. -Then we can create a channel using `newChannel`, which returns a `Channel[T]`. It uses MPMC internally, which stands for "multiple producer, multiple consumer". The `elements` parameter is used to specify whether a channel is buffered or not. For an unbuffered channel, the sender and the receiver block until the other side is ready. Sending data to a buffered channel blocks only when the buffer is full. Receiving data from a buffered channel blocks when the buffer is empty. +Then we can create a channel using `newChannel` proc with the following signature: +```nim +proc newChannel*[T](elements = 30): Channel[T] +``` +The new Nim channel is based on MPMC queue architecture internally, which stands for "multiple producer, multiple consumer". As you can see, `newChannel` is a generic proc - you can specify the type of the data you want to send or receive. The proc returns a `Channel[T]` and takes a single parameter `elements` with 30 as the default and which is used to specify whether a channel is buffered (`elements` = 1) or not (`elements` > 1). For an unbuffered channel, the sender and the receiver block until the other side is ready. Sending data to a buffered channel blocks only when the buffer is full. Receiving data from a buffered channel blocks when the buffer is empty. -`newChannel` is a generic proc - you can specify the types of the data you want to send or receive. +Here's three examples of initializing a channel: ```nim var chan1 = newChannel[int]() From fefc4eb4603d4128633c0bd4ae9aa73ec017543a Mon Sep 17 00:00:00 2001 From: flywind Date: Wed, 31 Mar 2021 18:06:04 +0800 Subject: [PATCH 35/37] better --- jekyll/_posts/2021-03-12-new-Nim-channels.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/jekyll/_posts/2021-03-12-new-Nim-channels.md b/jekyll/_posts/2021-03-12-new-Nim-channels.md index 15309ff8e..4078ab176 100644 --- a/jekyll/_posts/2021-03-12-new-Nim-channels.md +++ b/jekyll/_posts/2021-03-12-new-Nim-channels.md @@ -8,7 +8,7 @@ excerpt: "The new channels library is efficient and safe to use" # The new Nim channels implementation for ORC -Version 1.4 ships with the so-called ORC memory management algorithm. ORC is the existing ARC algorithm (first shipped in version 1.2) plus a cycle collector. The Nim devel branch introduces a new module called `std/isolation`, which allows us to pass `Isolated` data to threads safely and easily - it prevents data races at compile time. Another recent addition to the devel branch is `std/channels`, which is designed for ORC. It combines `Isolated` data and `channels`, and is efficient and safe to use. +Version 1.4 ships with the so-called ORC memory management algorithm. ORC is the existing ARC algorithm (first shipped in version 1.2) plus a cycle collector. The Nim devel branch introduces a new module called `std/isolation`, which allows us to pass `Isolated` data to threads safely and easily - it prevents data races at compile time. An `Isolated[T]` value owns data of type `T` that no one else has access to - hence it is isolated. This is also called an isolated subgraph. Another recent addition to the devel branch is `std/channels`, which is designed for ORC. It combines `Isolated` data and `channels`, and is efficient and safe to use. **Note:** At the time of writing, to compile the below code you need to use a development version of the Nim compiler (from 2021-03-12 or later). However, it should also work in Nim 1.6.0 or later. From 2f9ed9a956477c5dee4b23273c32235a348d88fb Mon Sep 17 00:00:00 2001 From: narimiran Date: Mon, 3 May 2021 08:33:56 +0200 Subject: [PATCH 36/37] minor whitespace changes --- jekyll/_posts/2021-03-12-new-Nim-channels.md | 21 ++++++++++++++++---- 1 file changed, 17 insertions(+), 4 deletions(-) diff --git a/jekyll/_posts/2021-03-12-new-Nim-channels.md b/jekyll/_posts/2021-03-12-new-Nim-channels.md index 4078ab176..193cf1dbe 100644 --- a/jekyll/_posts/2021-03-12-new-Nim-channels.md +++ b/jekyll/_posts/2021-03-12-new-Nim-channels.md @@ -1,31 +1,35 @@ --- - title: "The new Nim channels implementation for ORC" author: xflywind excerpt: "The new channels library is efficient and safe to use" - --- + # The new Nim channels implementation for ORC Version 1.4 ships with the so-called ORC memory management algorithm. ORC is the existing ARC algorithm (first shipped in version 1.2) plus a cycle collector. The Nim devel branch introduces a new module called `std/isolation`, which allows us to pass `Isolated` data to threads safely and easily - it prevents data races at compile time. An `Isolated[T]` value owns data of type `T` that no one else has access to - hence it is isolated. This is also called an isolated subgraph. Another recent addition to the devel branch is `std/channels`, which is designed for ORC. It combines `Isolated` data and `channels`, and is efficient and safe to use. **Note:** At the time of writing, to compile the below code you need to use a development version of the Nim compiler (from 2021-03-12 or later). However, it should also work in Nim 1.6.0 or later. + ## Background A channel is a model for sharing memory via message passing. A thread is able to send or receive messages over a channel. It's like sending a letter to your friend: the postman is the channel, and your friend is the receiver. You might already know `system/channels_builtin`, which is the old channels implementation. What's better in the new implementation? With the old one, first you need to copy your letter and send the copy to your friend. Then your friend may mark something on the copied letter and it won't affect the original. This works fine, but it is not efficient. If you use the new implementation, you only need to put your letter in the mailbox. No need to copy it! + ## The advantages - Designed for ARC/ORC, no legacy code - No need to `deepCopy`, just move data around - No data races + + ## Explore the new channels **Note:** Be sure to compile your code with `--gc:orc –-threads:on -d:ssl`. + ### Let's crawl the web **todo_urls.json** @@ -41,10 +45,13 @@ The main thread prepares tasks by reading `todo_urls.json`, and then it sends JS ```nim import std/channels import std/[httpclient, isolation, json] + var ch = newChannel[JsonNode]() # we need to send JsonNode + proc download(client: HttpClient, url: string) = let response = client.get(url) echo "content: ", response.body[0 .. 20] # prints the results + proc crawl = var client = newHttpClient() # the crawler defer: client.close() @@ -52,14 +59,17 @@ proc crawl = if data != nil: for url in data["url"]: download(client, url.getStr) + proc prepareTasks(fileWithUrls: string): seq[Isolated[JsonNode]] = result = @[] for line in lines(fileWithUrls): result.add isolate(parseJson(line)) # parse JSON file + proc spawnCrawlers = var tasks = prepareTasks("todo_urls.json") for t in mitems tasks: # we need a mutable view of the items ch.send move t + var thr: Thread[void] createThread(thr, crawl) # create crawl thread spawnCrawlers() @@ -72,6 +82,7 @@ Then we can create a channel using `newChannel` proc with the following signatur ```nim proc newChannel*[T](elements = 30): Channel[T] ``` + The new Nim channel is based on MPMC queue architecture internally, which stands for "multiple producer, multiple consumer". As you can see, `newChannel` is a generic proc - you can specify the type of the data you want to send or receive. The proc returns a `Channel[T]` and takes a single parameter `elements` with 30 as the default and which is used to specify whether a channel is buffered (`elements` = 1) or not (`elements` > 1). For an unbuffered channel, the sender and the receiver block until the other side is ready. Sending data to a buffered channel blocks only when the buffer is full. Receiving data from a buffered channel blocks when the buffer is empty. Here's three examples of initializing a channel: @@ -88,11 +99,12 @@ The `send` proc takes data that we want to send to the channel. The passed data There are two useful procs for a receiver: `recv` and `tryRecv`. `recv` blocks until something is sent to the channel. In contrast, `tryRecv` doesn't block - if no message exists in the channel, it just fails and returns `false`. We can write a while loop to call `tryRecv` and handle a message when available. + + ### It is safe and convenient The Nim compiler rejects the program below. It says that `expression cannot be isolated: s`. This is because `s` is a `ref object` - it may be modified somewhere and is not unique, so the variable cannot be isolated. - ```nim import std/[channels, json, isolation] @@ -133,7 +145,7 @@ By means of `Isolated` data, the channels become safer and more convenient to us Here is a simple benchmark. We create 40 threads that send data to the channel and 5/10/20 threads that receive from it. ```nim -# benchmark the new channel implementation with +# benchmark the new channel implementation with # `nim r --threads:on --gc:orc -d:newChan -d:danger app.nim` # # benchmark the old channel implementation with @@ -189,6 +201,7 @@ The new channels implementation makes ORC suitable for sharing data between thre If you use the latest devel, you can run the example above and experiment with `std/channels` in your own programs. Please try it out and give us your feedback! + ## Further information - [Isolated data for Nim](https://github.com/nim-lang/RFCs/issues/244) From c1126075365e183f139e9a3b5688cdddb238c079 Mon Sep 17 00:00:00 2001 From: narimiran Date: Mon, 3 May 2021 08:34:39 +0200 Subject: [PATCH 37/37] change the date --- ...1-03-12-new-Nim-channels.md => 2021-05-03-new-nim-channels.md} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename jekyll/_posts/{2021-03-12-new-Nim-channels.md => 2021-05-03-new-nim-channels.md} (100%) diff --git a/jekyll/_posts/2021-03-12-new-Nim-channels.md b/jekyll/_posts/2021-05-03-new-nim-channels.md similarity index 100% rename from jekyll/_posts/2021-03-12-new-Nim-channels.md rename to jekyll/_posts/2021-05-03-new-nim-channels.md