英文:
Channel non-determinism using context timeouts, deadlocks
问题
我正在尝试理解Go语言中的上下文(contexts)和通道(channels),但是我对正在发生的事情感到困惑。以下是一些示例代码。
package main
import (
"context"
"fmt"
"log"
"time"
"golang.org/x/time/rate"
)
func main() {
msgs := make(chan string)
ctx, cancel := context.WithTimeout(context.Background(), 1*time.Second)
defer cancel()
limiter := rate.NewLimiter(rate.Every(2*time.Second), 1)
go func(ctx context.Context, limiter *rate.Limiter) {
for {
limiter.Wait(context.Background())
select {
case <-ctx.Done():
log.Printf("finished!")
return
case msg := <-msgs:
log.Printf("receiving a message: %s", msg)
}
}
}(ctx, limiter)
defer close(msgs)
i := 0
for {
msgs <- fmt.Sprintf("sending message %d", i)
i++
if i > 10 {
break
}
}
}
我得到的结果是不确定的。有时日志会打印出三条消息,有时是五条。而且,程序每次都会陷入死锁:
2021/12/31 02:07:21 receiving a message: sending message 0
2021/12/31 02:07:23 receiving a message: sending message 1
2021/12/31 02:07:25 receiving a message: sending message 2
2021/12/31 02:07:27 receiving a message: sending message 3
2021/12/31 02:07:29 receiving a message: sending message 4
2021/12/31 02:07:29 finished!
fatal error: all goroutines are asleep - deadlock!
所以,我有几个问题:
- 为什么我的goroutine在一秒后没有结束?
- 为什么会发生死锁?我如何避免这种类型的死锁?
英文:
I'm trying to understand contexts and channels in Go, but I'm having trouble wrapping my head around what's happening. Here's some example code.
package main
import (
"context"
"fmt"
"log"
"time"
"golang.org/x/time/rate"
)
func main() {
msgs := make(chan string)
ctx, cancel := context.WithTimeout(context.Background(), 1*time.Second)
defer cancel()
limiter := rate.NewLimiter(rate.Every(2*time.Second), 1)
go func(ctx context.Context, limiter *rate.Limiter) {
for {
limiter.Wait(context.Background())
select {
case <-ctx.Done():
log.Printf("finished!")
return
case msg := <-msgs:
log.Printf("receiving a message: %s", msg)
}
}
}(ctx, limiter)
defer close(msgs)
i := 0
for {
msgs <- fmt.Sprintf("sending message %d", i)
i++
if i > 10 {
break
}
}
}
The results I'm getting are non-deterministic. Sometimes the logger prints out three messages, sometimes it's five. Also, the program ends in a deadlock every time:
2021/12/31 02:07:21 receiving a message: sending message 0
2021/12/31 02:07:23 receiving a message: sending message 1
2021/12/31 02:07:25 receiving a message: sending message 2
2021/12/31 02:07:27 receiving a message: sending message 3
2021/12/31 02:07:29 receiving a message: sending message 4
2021/12/31 02:07:29 finished!
fatal error: all goroutines are asleep - deadlock!
So, I guess I have a couple of questions:
- Why doesn't my goroutine simply end after one second?
- Why is there a deadlock? How can I avoid deadlocks of this nature?
答案1
得分: 3
为什么我的 goroutine 在一秒后没有结束?
在这里,goroutine 可能会等待而不是使用 select
:
limiter.Wait(context.Background())
为什么会出现死锁?如何避免这种类型的死锁?
主 goroutine 被卡住了。问题出在这里:
msgs <- fmt.Sprintf("sending message %d", I)
没有任何 goroutine 会从 msgs
中读取数据,所以它会一直等待。
以下是一种使其正常工作的方法:
package main
import (
"context"
"fmt"
"log"
"time"
"golang.org/x/time/rate"
)
func main() {
msgs := make(chan string)
ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
defer cancel()
limiter := rate.NewLimiter(rate.Every(1*time.Second), 1)
go func() {
for {
limiter.Wait(context.Background())
select {
case <-ctx.Done():
log.Printf("finished!")
return
case msg := <-msgs:
log.Printf("receiving a message: %s", msg)
}
}
}()
defer close(msgs)
for i := 0; i < 100000; i++ {
select {
case msgs <- fmt.Sprintf("sending message %d", i):
case <-ctx.Done():
log.Printf("finished too!")
return
}
}
}
英文:
> Why doesn't my goroutine simply end after one second?
While the goroutine may wait here instead of the select
:
limiter.Wait(context.Background())
> Why is there a deadlock? How can I avoid deadlocks of this nature?
It is your main goroutine which is getting stuck. It happens here:
msgs <- fmt.Sprintf("sending message %d", I)
There are no goroutines that would read from msgs
, so it waits forever.
Here is one of the ways to make it work:
package main
import (
"context"
"fmt"
"log"
"time"
"golang.org/x/time/rate"
)
func main() {
msgs := make(chan string)
ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
defer cancel()
limiter := rate.NewLimiter(rate.Every(1*time.Second), 1)
go func() {
for {
limiter.Wait(context.Background())
select {
case <-ctx.Done():
log.Printf("finished!")
return
case msg := <-msgs:
log.Printf("receiving a message: %s", msg)
}
}
}()
defer close(msgs)
for i := 0; i < 100000; i++ {
select {
case msgs <- fmt.Sprintf("sending message %d", i):
case <-ctx.Done():
log.Printf("finished too!")
return
}
}
}
答案2
得分: 1
使用<-
发送或接收消息是一个阻塞操作。当上下文超时结束时,goroutine已经退出,调用者无法继续执行。
// Goroutine已经结束,这个调用永远不会结束
msgs <- fmt.Sprintf("发送消息 %d", i)
此时程序发生了死锁。
至于非确定性,goroutine和主执行上下文并发运行。由于有两个没有太多延迟的for
循环,线程彼此竞争。不能保证它们每次都以相同的方式执行。
英文:
Sending or receiving a message using <-
is a blocking operation. When the context timeout finishes, the goroutine has now exited and the caller can't proceed.
// Goroutine has finished, this call will never finish
msgs <- fmt.Sprintf("sending message %d", i)
At this point the program has resulted in a deadlock.
As for the non-determinism, the goroutine and the main execution context run concurrently. Because there are two for
loops without much delay, the threads are competing with each other. There is no guarantee that they will execute in the same fashion every time.
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论