使用上下文超时、死锁的通道不确定性
Channel non-determinism using context timeouts, deadlocks
我试图理解 Go 中的上下文和通道,但我无法理解正在发生的事情。这是一些示例代码。
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 没有在一秒后结束?
- 为什么会出现死锁?我怎样才能避免这种性质的僵局?
Why doesn't my goroutine simply end after one second?
虽然 goroutine 可能会在这里等待而不是 select
:
limiter.Wait(context.Background())
Why is there a deadlock? How can I avoid deadlocks of this nature?
卡住的是您的主协程。它发生在这里:
msgs <- fmt.Sprintf("sending message %d", I)
没有可以从 msgs
读取的 goroutines,所以它会一直等待。
以下是实现它的方法之一:
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
}
}
}
使用<-
发送或接收消息是一个阻塞操作。当上下文超时结束时,goroutine 现在已经退出,调用者无法继续。
// Goroutine has finished, this call will never finish
msgs <- fmt.Sprintf("sending message %d", i)
此时程序出现了死锁。
至于非确定性,goroutine和主执行上下文运行并发。因为有两个 for
循环,没有太多延迟,所以线程在相互竞争。不能保证它们每次都会以相同的方式执行。
我试图理解 Go 中的上下文和通道,但我无法理解正在发生的事情。这是一些示例代码。
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 没有在一秒后结束?
- 为什么会出现死锁?我怎样才能避免这种性质的僵局?
Why doesn't my goroutine simply end after one second?
虽然 goroutine 可能会在这里等待而不是 select
:
limiter.Wait(context.Background())
Why is there a deadlock? How can I avoid deadlocks of this nature?
卡住的是您的主协程。它发生在这里:
msgs <- fmt.Sprintf("sending message %d", I)
没有可以从 msgs
读取的 goroutines,所以它会一直等待。
以下是实现它的方法之一:
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
}
}
}
使用<-
发送或接收消息是一个阻塞操作。当上下文超时结束时,goroutine 现在已经退出,调用者无法继续。
// Goroutine has finished, this call will never finish
msgs <- fmt.Sprintf("sending message %d", i)
此时程序出现了死锁。
至于非确定性,goroutine和主执行上下文运行并发。因为有两个 for
循环,没有太多延迟,所以线程在相互竞争。不能保证它们每次都会以相同的方式执行。