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
在这里再进行打印看一下结果,你会发现代码是有问题的。
为什么将小写的字母打印出来,而打印大写字母时发生了报错呢?
这个就要追溯到代码中了,因为我们代码本身就写的有问题。
还是回归到本文长谈的一个问题,那就是对于所有的channel有发送数据就必须有接收数据,如果没有接收数据就会报错。
So in the code, can you tell that the block only sends data but does not receive data?
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.
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.
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.
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中把任务数添加进去,然后在代码最后添加一个等待即可。
关于这块的内容先知道这么用即可,咔咔后期会慢慢的补充并且深入。
这块的代码看起来不是那么的完美的,接下来抽象一下。
这块代码有没有发现有点蹩脚,接下来我们使用函数式编程进行简单的处理。
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
这块代码看不明白就先放着,写的时间长了,你就会明白其中的含义了,学习东西不要钻牛角尖。
开头先给一个问题,假设现在有俩个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管道。
在上文写的createWorker
和worker
俩个方法还记得吧!接下来就不在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)得知也是没有问题的。
这段代码虽然运行没有任何问题,但是这样有什么缺点呢?
可以看下这段代码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 又会阻塞住,这个是不好的。
这种模式是在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)为0、7、12、20…中间很多的数字都没来得急打印。
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)没有漏掉数据,并且也是无序的,这样就非常好了。
上面的这个程序是退出不了的,我们想让它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.
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)
这样就可以每秒来显示一下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!