This is the 23rd day of my participation in the August More Text Challenge

Goroutine&channel practice

This article shares two examples to help you understand the combined use of Goroutine and channel

Use a channel to wait for the goroutine to end

As an example from the previous article (click here to view it), we have improved it, and the source code is as follows:

package main import ( "fmt" ) func doWorker(id int, c chan int) { for n := range c { fmt.Printf("Worker %d received %c\n", id, n) } } func createWorker(id int) chan<- int { c := make(chan int) go doWorker(id,c) return c } func chanDemo() { var channels [10]chan<- int for i := 0; i < 10; I ++ {channels[I] = createWorker(I)} i < 10; i++ { channels[i] <- 'a' + i } for i := 0; i < 10; i++ { channels[i] <- 'A' + i } } func main() { chanDemo() }Copy the code

10 channels were created and 10 Goroutines were created. An uppercase letter and a lowercase letter are sent to each channel, and the data in the channel is printed through the goroutine

Now the question is, after each goroutine is printed, how do you tell the outside world that it’s finished printing

At the end of my last article, I left the line, “Do not communicate over shared memory; Share memory through communication. So let’s first modify the above program like this:

Create a structure with two channels, one for sending data (in) and one for notifying that it is received (done).

package main import ( "fmt" ) func doWorker(id int, c chan int, Done chan bool) {for n := range c {fmt.Printf("Worker %d received %c\n", id, n) done < -true, }} type worker struct {in chan int done chan bool} func createWorker(id int) worker {w := worker{ in: make(chan int), done: make(chan bool), } go doWorker(id, w.in, w.done) return w } func chanDemo() { var workers [10]worker for i := 0; i < 10; I ++ {// create 10 worker workers[I] = createWorker(I)} // Send data to 10 channels for I := 0; i < 10; I ++ {workers[I]. In <- 'a' + I <-workers[I]. Done} for I := 0; i < 10; i++ { workers[i].in <- 'A' + i <-workers[i].done } } func main() { chanDemo() }Copy the code

If you print the above results, you will find that the results are a, B, C, D, e, f….. It was printed in this order. If it is printed sequentially, it will be meaningless. There is no need to build 10 workers, just print them one by one. We don’t want to send one piece of data down the channel and wait for it to end, we want to send 20 pieces of data out at a time and then process the finished mark all at once

Therefore, you simply remove the <-workers[I]. Done statements in the two for loops that send data, and then loop through done in workers. Therefore, modify the chanDemo function as follows:

func chanDemo() { var workers [10]worker for i := 0; i < 10; I ++ {workers[I] = createWorker(I)}, worker := range workers{worker. In <- 'a' + I} for I, worker := range workers { worker.in <- 'A' + i } //wait for all of them for _, Done <-worker.done <-worker.done}}Copy the code

When executing, all lowercase letters are printed, but uppercase letters are blocked. The reason is that after the first loop that sends data to worker.in sends data, when goroutine receives the print, it sends data true to each worker.done, but there is no receiving operation, so the loop sends data to worker. When the worker function is executed, Because each woker.done is already blocked there, an error is reported

A less formal approach is to send true to worker.done and open a separate Goroutine to handle it

go func() {done <- true}()
Copy the code

To help with waiting for multiple people to complete a task, the Go library provides a waitGroup method

The use of waitGroup

Let’s use waitGropu to modify the original program

package main import ( "fmt" "sync" ) func doWorker(id int, c chan int, wg *sync.WaitGroup) { for n := range c { fmt.Printf("Worker %d received %c\n", id, n) wg.Done() } } type worker struct { in chan int wg *sync.WaitGroup } func createWorker(id int, wg *sync.WaitGroup) worker { w := worker{ in: make(chan int), wg: wg, } go doWorker(id, w.in, Wg) return w} func chanDemo() {var wg sync.waitgroup var workers [10]worker for I := 0; i < 10; I ++ {workers[I] = createWorker(I, &wg)} wg.add (20) Worker := range worker {worker.in <- 'a' + I // wg.add (1)} for I, Worker := range workers {worker.in <- 'A' + I} wg.add (1)} wg.wait ()} func main() {chanDemo()}Copy the code

You can see that the execution results are printed with a mixture of upper and lower case letters. It means it’s printed in parallel

This shows how to use go’s built-in waitGroup to wait for multiple people

Use channels to traverse the tree

In the functional programming that article shared a way through the transfer function to achieve tree traversal and node number statistics (not clear through the functional programming way to achieve tree traversal and statistics, you can click here), below is the source code

tree/node.go package tree import "fmt" type Node struct { Value int Left, Right *Node} func (n *Node) Print() {fmt.println (n.value)} func (n *Node) Traverse() {// n.TraverseFunc(func(node *Node) { fmt.Println(node.Value) }) fmt.Println() } func (n *Node) TraverseFunc(f func(node *Node)) { if n == nil { return } n.Left.TraverseFunc(f) f(n) n.Right.TraverseFunc(f) } =================================  entry/entry.go package main import ( "fmt" "google.go/part6/functional/closure/tree" ) func main() { var root tree.Node  root = tree.Node{Value: 1} root.Left = &tree.Node{Value: 2} root.Right = &tree.Node{Value: 3} root.Left.Right = &tree.Node{Value: 4} root.Right.Left = &tree.Node{Value: 5} root.traverse () // If you want to count nodes, NodeCount := 0 root.TraverseFunc(func(node * tree.node) {nodeCount++}) FMT.Println(" nodeCount: ", nodeCount)}Copy the code

On this basis, another channel method is added to find the largest node in the tree, as follows:

Tree /node.go TraversWithChannel() chan * node {out := make(chan * node) TraversWithChannel() *Node go func() {node.traversefunc (func(Node *Node) {out<-node}) close(out)}() return out} = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = entry/entry. Go c: = root. TraversWithChannel () / / every time to get to a channel maxNode: = 0 for the node If node.Value > maxNode {maxNode = node.Value}} fmt.Println("Max node  Value:", maxNode)Copy the code

This is a simple use of Goroutine and channel, relatively simple. As a go expert, goroutine execution sequence, channel receiving, closing and so on are still confused. Especially, goroutine support has changed in different versions of GO language (1.14 and earlier versions). We still need to learn the internal scheduling principle of Goroutine in depth. To use the Goroutine. If the above content is incorrect, please correct it