请问大家这里如何打印出“End. [sender]”

309 天前
 liaowb3

大家好,这是我的代码:

func main () {
    // synChan := make(chan struct{}, 1)
    intChan := make(chan int, 1)
    ticker := time.NewTicker(time.Second)
    go func() {
        for range ticker.C {
            select {
            case intChan <- 1:
            case intChan <- 2:
            case intChan <- 3:
            }
        }
        // synChan<- struct{}{}
        fmt.Println("End [SEND]")
    }()

    var sum int

    for e := range intChan {
        fmt.Printf("接收到  %d\n", e)
        sum += e
        if sum > 3 {
            fmt.Println(sum)
            break
        }
    }

    fmt.Println("End [REC]")

    // <-synChan
}

这是《 GO 并发编程》里讲通道的一个题目,但是我试了几个方法之后会成死锁,请问大家如何打印出这个 fmt.Println("End [SEND]")。这里为什么不能打印出来?我想过是 main 函数退出了,所以试着使用通道阻塞,但直接会出死锁。。。

1550 次点击
所在节点    Go 编程语言
9 条回复
sexoutsex2011
309 天前
我蛮帮你问了大模型,你试试看:

这段 Go 代码的主要问题是,它在等待一个信号来表明发送 goroutine 已经结束,但是这个信号从未被发送。当`sum`大于 3 时,接收循环会中断,但是发送 goroutine 会因为`intChan`的缓冲区已满而阻塞,导致无法到达打印`"End [SEND]"`的代码行。

要解决这个问题,你需要确保发送 goroutine 有机会发送完成信号。在这个场景中,你可以通过关闭`intChan`来实现,因为关闭一个通道会导致所有接收者收到一个零值,并且随后的接收都会立即返回零值,而不会阻塞。因为`intChan`是一个 int 类型的通道,接收者会得到一个`0`。

以下是修正后的代码,它确保了打印出`"End [SEND]"`:

```go
package main

import (
"fmt"
"time"
)

func main() {
intChan := make(chan int, 1)
ticker := time.NewTicker(time.Second)
done := make(chan struct{}) // 添加一个完成通道

go func() {
for range ticker.C {
select {
case intChan <- 1:
case intChan <- 2:
case intChan <- 3:
}
}
ticker.Stop() // 停止 ticker
close(intChan) // 关闭 intChan
fmt.Println("End [SEND]")
close(done) // 发送完成信号
}()

var sum int

for e := range intChan {
fmt.Printf("接收到 %d\n", e)
sum += e
if sum > 3 {
fmt.Println(sum)
break
}
}

fmt.Println("End [REC]")

<-done // 等待发送 goroutine 完成
}
```

这里做了以下改动:

1. 添加了一个`done`通道,用来接收发送 goroutine 完成的信号。
2. `ticker.Stop()`被调用来停止 ticker ,避免资源泄露。
3. 在发送循环结束后关闭`intChan`通道。
4. 打印`"End [SEND]"`后,关闭`done`通道来发送一个完成信号。
5. 在主函数的最后,添加`<-done`来等待发送 goroutine 完成。

现在程序会等待发送 goroutine 结束后,才结束`main`函数,确保`"End [SEND]"`被打印出来。请注意,这个版本的代码假设发送 goroutine 总是会结束,否则可能会导致死锁。在实际编程中,你可能需要添加超时或其他机制来处理这种情况。
AoEiuV020JP
309 天前
@sexoutsex2011 #1 这个 ai 的答案你试过没有,这里是禁止搬运 ai 回答的,
liaowb3
309 天前
@sexoutsex2011 不行,死锁了
Rehtt
309 天前
time.NewTicker 是创建了一个周期定时器,下面没有调用 ticker.Stop()来终止定时器,所以 for range ticker.C 回一直循环不会退出
PTLin
309 天前
你这个不叫死锁好吧,你要在适当的时候调用 ticker.Stop()让这个 Ticker 停下来,让 channel 被 close
Kisesy
309 天前
你接收完数据了,不向发送方发送结束命令,它还在发数据呢
Great233
309 天前
ticker.Stop() 停止计时器后只是不再发送数据,但是 channel 并没有被关闭
https://pkg.go.dev/time#Ticker.Stop

```
func main() {
intChan := make(chan int, 1)
ticker := time.NewTicker(time.Second)
end := make(chan int)
go func() {
loop:
for range ticker.C {
select {
case intChan <- 1:
case intChan <- 2:
case intChan <- 3:
case <-end:
fmt.Println("End [SEND]")
ticker.Stop()
break loop
}
}
}()

var sum int

for e := range intChan {
fmt.Printf("接收到 %d\n", e)
sum += e
if sum > 3 {
fmt.Println(sum)
end <- 1
break
}
}
fmt.Println("End [REC]")
}
```
liaowb3
309 天前
@Great233 soga ,我懂了,谢谢大佬
sexoutsex2011
308 天前
@AoEiuV020JP 抱歉抱歉

这是一个专为移动设备优化的页面(即为了让你能够在 Google 搜索结果里秒开这个页面),如果你希望参与 V2EX 社区的讨论,你可以继续到 V2EX 上打开本讨论主题的完整版本。

https://www.v2ex.com/t/1016537

V2EX 是创意工作者们的社区,是一个分享自己正在做的有趣事物、交流想法,可以遇见新朋友甚至新机会的地方。

V2EX is a community of developers, designers and creative people.

© 2021 V2EX