Let's talk about concurrent programming in Go (2)

咔咔
Release: 2021-07-07 16:37:45
Original
1646 people have browsed it

Let’s talk about Go’s goroutine and Channel

    • 1. Use channel to wait for the task to end
      • Usage of sync.WaitGroup
      • Abstract code
    • 2. Use select for scheduling
      • Use of timer
    • 3. Summary

    Recommended related articles: "Talk about concurrent programming in Go (1)"

    1. Use channel to wait for the task to end

    The use case is still the code written in the second section of the first article, but only one paragraph is needed here.

    package mainimport (
    	"fmt"
    	"time")func createWorker(id int) chan
    Copy after login

    Here Kaka puts the original source code here. If you want to follow the rhythm of the article, you can put it in your editor and operate it.

    Then what is the problem with this code?

    You can see that a sleep is used at the end of the channelDemo function. This thing cannot be used indiscriminately in the program.

    Speaking of which, let me tell you a little story. Kaka saw a piece of code on the Internet that added sleep.

    Then a novice programmer didn’t understand why this sleep was added, and then asked the project manager. The project manager said that after the boss found that the program was slow, he would ask us to optimize it, and every optimization would remove this sleep. Just shorten the time. Let the boss feel that we are doing something good.

    Novices will mark the code if they don’t understand it, and then write a comment, "The project manager asked for slow running here, and when the boss asked for optimization, the code was significantly faster."

    Unfortunately, this sentence was seen by the boss. The boss didn’t know the code, but he still knew the text! So, the project manager stepped down.

    So most of sleep is in a testing state and will never appear online, so what? It is necessary to solve this sleep in the code.

    那么大家在回忆一下,在这里为什么要加sleep呢?

    发送到channel的数据都是在另一个goroutine中进行并发打印的,并发打印就会出现问题,因为根本不会知道什么时候才打印完毕。

    所以说这个sleep就会为了应对这个不知道什么时候打印完的问题,给个1毫秒让进行打印。

    这种做法是非常不好的,接下来看看使用一种新的方式来解决这个问题。

    以下代码是修改完的代码。

    package mainimport (
    	"fmt")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 doWorker(id int, c chan int, done chan bool) {
    	for n := range c {
    		fmt.Printf("Worker %d receive %c\n", id, n)
    		done 
    Copy after login
    Copy after login

    将这些代码复制到你的本地,然后再来看一下都做了什么改动。

    • First of all, for the convenience of parameter passing, we created a structure worker
    • and changed the previous worker method to doWorker
    • At this time, the return value of the createWorker method cannot be It is the previous channel, but the created structure worker
    • and then all channels are created in the createWorker method. And use the structure to pass parameters to doWorker.
    • The final return is the structure.
    • The last step is to receive the value of workers[i] in the two loops that send data in the channelDemo method.

    Look at the printed results

    Lets talk about concurrent programming in Go (2)

    ##Are you a little confused? , how can this be in order? If it is parallel, it is necessary to open those 10 workers and just print in order.

    Let's solve this problem now, I don't want to send a task and wait for it to end.

    The best thing is to send them all out and wait for them all to finish before exiting.

    代码实现如下

    package mainimport (
    	"fmt")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 doWorker(id int, c chan int, done chan bool) {
    	for n := range c {
    		fmt.Printf("Worker %d receive %c\n", id, n)
    		done 
    Copy after login
    Copy after login

    在这里再进行打印看一下结果,你会发现代码是有问题的。

    Lets talk about concurrent programming in Go (2)

    为什么将小写的字母打印出来,而打印大写字母时发生了报错呢?

    这个就要追溯到代码中了,因为我们代码本身就写的有问题。

    还是回归到本文长谈的一个问题,那就是对于所有的channel有发送数据就必须有接收数据,如果没有接收数据就会报错。

    So in the code, can you tell that the block only sends data but does not receive data?

    Lets talk about concurrent programming in Go (2)

    The problem is that after sending lowercase letters to the channel, it will enter the doWorker method, and then give done sends a true, but the method to receive done is at the back, that is to say, when the second uppercase letter is sent, a cyclic wait will be sent.

    Solving this problem is also very simple, we only need to send done concurrently.

    Lets talk about concurrent programming in Go (2)

    The printed result is also correct.

    The case given in this article will not appear in ordinary projects, so there is no need to worry about it.

    The case given is just to make everyone more familiar with the channel mechanism.

    There is another solution to this solution, please see the code.

    Lets talk about concurrent programming in Go (2)

    Restore the code to before, and then loop to receive done under each sent letter.

    For this multi-tasking waiting method, there is a library in go that can do this. Let’s take a look.

    Usage of sync.WaitGroup

    I will not introduce the usage of sync.WaitGroup one by one. Simply take a look at the source code implementation.

    package mainimport (
    	"fmt"
    	"sync")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 doWorker(id int, c chan int, wg *sync.WaitGroup) {
    	for n := range c {
    		fmt.Printf("Worker %d receive %c\n", id, n)
    		wg.Done()
    	}}func channelDemo() {
    	var wg sync.WaitGroup	var workers [10]worker	for i := 0; i 
    Copy after login

    这份源码也是非常简单的,具体修改得东西咔咔简单介绍一下。

    • 首先取消了channelDemo这个方法中关于done的channel。
    • 使用了sync.WaitGroup,并且给createWorker方法传递sync.WaitGroup
    • createWorker方法使用了 worker的结构体。
    • 所以要先修改worker结构体,将之前的done改为wg *sync.WaitGroup即可
    • 这样就可以直接用结构体的数据。
    • 接着在doWorker方法中把最后一个参数done改为wg *sync.WaitGroup
    • 将方法中的done改为wg.Done()
    • 最后一步就是回到函数channelDemo中把任务数添加进去,然后在代码最后添加一个等待即可。

    关于这块的内容先知道这么用即可,咔咔后期会慢慢的补充并且深入。

    抽象代码

    这块的代码看起来不是那么的完美的,接下来抽象一下。

    Lets talk about concurrent programming in Go (2)

    这块代码有没有发现有点蹩脚,接下来我们使用函数式编程进行简单的处理。

    package mainimport (
    	"fmt"
    	"sync")type worker struct {
    	in   chan int
    	done func()}func createWorker(id int, wg *sync.WaitGroup) worker {
    	w := worker{
    		in: make(chan int),
    		done: func() {
    			wg.Done()
    		},
    	}
    	go doWorker(id, w)
    	return w}func doWorker(id int, w worker) {
    	for n := range w.in {
    		fmt.Printf("Worker %d receive %c\n", id, n)
    		w.done()
    	}}func channelDemo() {
    	var wg sync.WaitGroup	var workers [10]worker	for i := 0; i 
    Copy after login

    这块代码看不明白就先放着,写的时间长了,你就会明白其中的含义了,学习东西不要钻牛角尖。

    二、使用select进行调度

    开头先给一个问题,假设现在有俩个channel,谁来的快先收谁应该怎么做?

    package mainimport (
    	"fmt"
    	"math/rand"
    	"time")func generator() chan int {
    	out := make(chan int)
    	go func() {
    		i := 0
    		for {
    			// 随机睡眠1500毫秒以内
    			time.Sleep(
    				time.Duration(rand.Intn(1500)) *
    					time.Millisecond)
    			// 往out这个channel发送i值
    			out 
    Copy after login

    以上就是代码实现,代码注释也写的非常的清晰明了,就不过多的做解释了。

    主要用法还是对channel的使用,在带上了一个新的概念select,可以在多个通道,那个通道先发送数据,就先执行谁,并且这个select也是可以并行执行channel管道。

    在上文写的createWorkerworker俩个方法还记得吧!接下来就不在select里边直接打印了。

    就使用之前写的俩个方法融合在一起,咔咔已将将源码写好了,接下来看一下实现。

    package mainimport (
    	"fmt"
    	"math/rand"
    	"time")func worker(id int, c chan int) {
    	for n := range c {
    		fmt.Printf("Worker %d receive %d\n", id, n)
    	}}func createWorker(id int) chan
    Copy after login
    Copy after login

    运行代码

    Lets talk about concurrent programming in Go (2)

    看到Lets talk about concurrent programming in Go (2)得知也是没有问题的。

    这段代码虽然运行没有任何问题,但是这样有什么缺点呢?

    Lets talk about concurrent programming in Go (2)

    可以看下这段代码n := 这里先收了一个值,然后在下边代码<code style="box-sizing: border-box; font-family: " source code pro sans mono menlo monaco consolas inconsolata courier monospace sc yahei sans-serif font-size: background-color: rgb border-radius: padding: line-height: color:>w 又会阻塞住,这个是不好的。

    那么希望是怎么执行的呢?

    Lets talk about concurrent programming in Go (2)

    这种模式是在select中既可以收数据,也可以发数据,目前这个程序是编译不过的,请看修改后的源码。

    package mainimport (
    	"fmt"
    	"math/rand"
    	"time")func worker(id int, c chan int) {
    	for n := range c {
    		fmt.Printf("Worker %d receive %d\n", id, n)
    	}}func createWorker(id int) chan
    Copy after login
    Copy after login

    这个模式还是有缺点的,因为n收c1和c2的速度跟消耗的速度是不一样的。

    假设c1的生成速度特别快,一下子生成了1,2,3。那么最后输出的数据有可能就只有3,而1和2就无法输出了。

    这个场景也是非常好模拟的,只需要在打印的位置加上一点延迟时间即可。

    Lets talk about concurrent programming in Go (2)

    此时你会看到Lets talk about concurrent programming in Go (2)为0、7、12、20…中间很多的数字都没来得急打印。

    因此我们就需要把收到的n存下来进行排队输出。

    package mainimport (
    	"fmt"
    	"math/rand"
    	"time")func worker(id int, c chan int) {
    	for n := range c {
    		// 手动让消耗速度变慢
    		time.Sleep(5 * time.Second)
    		fmt.Printf("Worker %d receive %d\n", id, n)
    	}}func createWorker(id int) chan 0 {
    			activeWorker = worker			// 取出索引为0的值
    			activeValue = values[0]
    		}
    		/**
    		select 方式进行调度
    		        使用场景:比如有多个通道,但我打算是哪一个通道先给我数据,我就先执行谁
    		        这个select 可以是并行执行 channel管道
    		*/
    		select {
    		case n := 
    Copy after login

    以上就是实现代码

    此时在来看Lets talk about concurrent programming in Go (2)。

    Lets talk about concurrent programming in Go (2)

    Lets talk about concurrent programming in Go (2)没有漏掉数据,并且也是无序的,这样就非常好了。

    计时器的使用

    上面的这个程序是退出不了的,我们想让它10s后就直接退出怎么做呢?

    那就需要使用计时器来进行操作了。

    package mainimport (
    	"fmt"
    	"math/rand"
    	"time")func worker(id int, c chan int) {
    	for n := range c {
    		// 手动让消耗速度变慢
    		time.Sleep(time.Second)
    		fmt.Printf("Worker %d receive %d\n", id, n)
    	}}func createWorker(id int) chan 0 {
    			activeWorker = worker			// 取出索引为0的值
    			activeValue = values[0]
    		}
    		/**
    		select 方式进行调度
    		        使用场景:比如有多个通道,但我打算是哪一个通道先给我数据,我就先执行谁
    		        这个select 可以是并行执行 channel管道
    		*/
    		select {
    		case n := 
    Copy after login
    Copy after login

    这里就是源码的实现,可以看到直接在select中是可以收到tm的值的,也就说如果到了10s,就会执行打印bye的操作。

    So now there is another requirement, that is, if the data has not been received within 800 milliseconds, other things can be done.

    Using the idea of ​​​​drawing inferences from one example, you can think about how this matter should be done.

    Lets talk about concurrent programming in Go (2)

    In fact, it is very simple. You only need to set a timer in the case.

    Now that I have mentioned this, I will add a usage for youLets talk about concurrent programming in Go (2) := time.Tick(time.Second)

    is also used in case.

    Lets talk about concurrent programming in Go (2)

    这样就可以每秒来显示一下values队列有多少数据。

    这块的内容就结束了,最终给大家发一下源码,感兴趣的可以在自己的编辑器上试试看。

    package mainimport (
    	"fmt"
    	"math/rand"
    	"time")func worker(id int, c chan int) {
    	for n := range c {
    		// 手动让消耗速度变慢
    		time.Sleep(time.Second)
    		fmt.Printf("Worker %d receive %d\n", id, n)
    	}}func createWorker(id int) chan 0 {
    			activeWorker = worker			// 取出索引为0的值
    			activeValue = values[0]
    		}
    		/**
    		select 方式进行调度
    		        使用场景:比如有多个通道,但我打算是哪一个通道先给我数据,我就先执行谁
    		        这个select 可以是并行执行 channel管道
    		*/
    		select {
    		case n := 
    Copy after login
    Copy after login

    三、总结

    本文主要就是对于goroutine和channel的大量练习。

    文中的案例,有可能会一时半会理解不了,是没有关系的,不用钻牛角尖的。

    When you have been swimming in the ocean of go for a long time, some things will naturally become clear to you.

    The next article is to give you a practical go concurrent crawler project.

    #Persistence in learning, persistence in writing, and persistence in sharing are the beliefs that Kaka has always adhered to since his career. I hope that Kaka’s articles on the huge Internet can bring you a little bit of help. I’m Kaka, see you next time.

    The above is the detailed content of Let's talk about concurrent programming in Go (2). For more information, please follow other related articles on the PHP Chinese website!

    Related labels:
    go
    source:php.cn
    Statement of this Website
    The content of this article is voluntarily contributed by netizens, and the copyright belongs to the original author. This site does not assume corresponding legal responsibility. If you find any content suspected of plagiarism or infringement, please contact admin@php.cn
    Popular Tutorials
    More>
    Latest Downloads
    More>
    Web Effects
    Website Source Code
    Website Materials
    Front End Template