Golang Go语言中请教 goroutine 通信写法问题

发布于 1周前 作者 wuwangju 来自 Go语言

image

第一种想法如上图: main goroutine 遍历这个 chan, 将所有数据读出. 但是问题是. 左边那三个 goroutine 如果结束了. main goroutine 如何结束? 岂不是阻塞了? 也没法关闭, 如果左边的其中一个关闭了. 那另外两个的数据就读不出来了.

第二种思路是创建三个 chan image

这个直接 close 就行了.

我的问题是: 第一种想法中的问题如何解决?

是不是实践中第二种想法比较符合套路?

确实是新手. 大佬勿喷.


Golang Go语言中请教 goroutine 通信写法问题

更多关于Golang Go语言中请教 goroutine 通信写法问题的实战系列教程也可以访问 https://www.itying.com/category-94-b0.html

36 回复

第一个用 sync.waitGroup 应该能解决吧

更多关于Golang Go语言中请教 goroutine 通信写法问题的实战系列教程也可以访问 https://www.itying.com/category-94-b0.html





可以是可以. 那 chan 的 buffer 得足够大.要不然就死锁了吧?

也可以再起一个 chan, 在想结束的时候通知 main 该结束了

明白了. 用 chan 或者 context
在主线程中用 select 检测那三个 goroutine. 好像可行. 不知道算不算最佳实践.

#3 我记得有第三方写的 infinite chan ,无限大缓冲区的 chan

Reading Material: https://go.dev/blog/pipelines

拆成两个问题:
1. 多个 goroutine 如何读取消息
- 使用 fan-in 和 fan-out pattern ,将其结果汇总到一个 channel 里,此时原始 goroutine 关闭 channel 不影响;
- 直接 select 多个 channel 。
2. 当某个 gorutine 退出时如何通知其他的 goroutine 退出:
a. (可选)如果需要等待其他 goroutine 退出的话,使用 sync.WaitGroup 等待;
b. 使用一个 exitChannel ( chan struct{} 就行),接收到退出信号的时候直接由 main close ,其他 goroutine 使用 for { select { case <- exitChannel: return default: logic} } 的形式来正确接受退出信号

没有换行写的有点乱,最后一条 case 和 default 是不同的分支

三个 goroutinue 在想结束的时候发一个消息到用来结束的 chan 里,在 main 里面 select 监听,在监听到三次之后,说明三个 groutinue 全部执行完了,return main 就好了
这种方法,是其他协程通知主协程自己结束了,主协程收到这个通知,再决定下一步怎样做
而 context 其实是 main 协程管理其他协程的,就是 main 想让其他协程结束时调用 cancel ,其他协程通过监听 ctx.Done(),就可以 return 了
复杂并发应用中 goutinue 之间的关系,其实是树状的,你想在一个树的节点,结束这个数下面的所有的子 goroutinue 时,就用 context ,在子 goutinue 中传递值也可以

你们真强,我都没看明白楼主在说啥。

一个 tcp 连接一个 goroutine , 结束, 不管客户端还是服务端都是。
加个 channel 就是脱裤子放屁

考虑下 main goroutine 为啥要结束??

也不是一定要结束, 而是继续往下走.

第二种吧,逻辑清晰,实现简单

往 channel 里写个结束的标记就行了吧,要不然就加个生存期管理

第一种消耗比较小啊,可以考虑增加规定个独特的终止信息在生产端退出时发出,消费端识别处理下(比较类似于 waitgroup ,消费端处理终止信息时也可以用 atomic 计数)

用 waitgroup 为什么会死锁?只是 routine 里因为 channel 满了,阻塞住,要等 channel 用空位了,才会塞入,然后继续运作。

三个生产者堵死. 因为 chan 满了.
消费者堵死, 因为一直在 wait.

都满了消费者怎么堵死,直接消费啊

你对 waitgroup 的使用有误解,add 和 wait 都是生产者侧调用的。跟 consumer 没关系,consumer 只需要循环读取 chan 消费。

另外,如果 producer 并发很高,建议多个 chan ;毕竟 chan 底层依赖 mutex ,main 里再 for select 消费

最简单就是加一个 waitgroup ,再起一个 goroutine 去 wait ,wait 到了就 close channel ,main routine 用 for range 去读。channel 。

套路都是 main routine 去 range channel ,剩下的问题就是如何 close channel ,这个建议你搜索“如何优雅关闭 channel”,学习 channel 使用的几个套路。

<br> ch := make(chan any)<br> var wg sync.WaitGroup<br> for i := 0; i &lt; 10; i++ {<br> wg.Add(1)<br> go func(done func()) {<br> defer done()<br> time.Sleep(time.Second * 3)<br> ch &lt;- <a target="_blank" href="http://rand.Int" rel="nofollow noopener">rand.Int</a>()<br> }(wg.Done)<br> }<br> go func() {<br> wg.Wait()<br> close(ch)<br> }()<br> for v := range ch {<br> log.Printf("v:%v", v)<br> }<br> log.Printf("这里等待执行")<br><br>

Done 应该要放在消费者里,不然假如消费者处理时间比较长的,会漏掉最后一条。

忽略我上面说的,没看到是 unbuffered

这个方案我研究过,头疼得很。最后选择手写 CAS 得 ringbuffer 的方案。

第一种写法按照官方的说法就是不要过分关注 chan 的关闭了, 毕竟没有数据了, chan 的占用很小, 程序如果结束自然也就交还操作系统了

close chan 两个原则
1. 不要在接收端关闭, 也就是你代码里的 main goroutine
2. 有多个同时写, 不要在写的地方关闭, 也就是你的代码中 goroutine1,2,3

所以最好的做法, 就是楼上给的那个代码, 先 waitGroup 够三个之后, 直接在 main 关闭就行了



谢谢 最后用的这个方法解决了.

package main

import (
“fmt”
“math/rand”
“sync”
“time”
)

func main() {

// init var
ch := make(chan int)
wg := &sync.WaitGroup{}

// goroutine1, 2, 3
for i := 0; i < 3; i++ {
wg.Add(1)
go task(wg, ch)
}

// read chan data
go func() {
for val := range ch {
fmt.Println(val)
}
}()

// wait group
wg.Wait()
close(ch)

// close fast, can`t read all chan data
time.Sleep(time.Second)
}

func task(wg *sync.WaitGroup, ch chan int) {
defer wg.Done()

ts := rand.Intn(3) + 1
time.Sleep(time.Second * time.Duration(ts))

ch <- ts
}

channel 为什么要关闭?

这里可以延伸一个问题,你上述例子用了 4 个 goroutine ,如果用 waitgroup ,就要用 5 个 goroutine ,问能否用 3 个 goroutine 解决问题?

1 、chan 关闭了里面的数据可以继续读,只是不可以写
2 、多个 goroutine 关闭用 context

在Go语言中,goroutine之间的通信通常通过通道(channel)来实现。通道是一种用于在不同goroutine之间传递数据的类型化管道。以下是goroutine通信的基本写法示例:

  1. 声明和初始化通道

    ch := make(chan int)
    
  2. 发送数据到通道: 在一个goroutine中,你可以使用箭头操作符(<-)将数据发送到通道中。

    go func() {
        ch <- 42 // 发送数据到通道
    }()
    
  3. 从通道接收数据: 在另一个goroutine中,你可以从通道中接收数据。

    result := <-ch // 从通道接收数据
    fmt.Println(result) // 输出42
    
  4. 关闭通道: 当没有更多的数据发送到通道时,可以关闭通道。注意,接收方应在关闭通道前完成数据的接收,否则会导致panic。

    close(ch)
    
  5. 使用带缓冲的通道: 如果希望通道有缓冲区,可以在make函数中指定缓冲区大小。

    bufferedCh := make(chan int, 10)
    
  6. select语句: 使用select语句可以监听多个通道操作,这对于处理多个通道通信时非常有用。

这些是使用Go语言进行goroutine通信的基本概念和写法。希望这些示例能帮助你理解如何在Go中实现goroutine之间的通信。如果有更多具体问题或需要进一步的示例,请随时提问。

回到顶部