使用上下文超时和死锁来处理通道的非确定性。

huangapple go评论75阅读模式
英文:

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 (
	&quot;context&quot;
	&quot;fmt&quot;
	&quot;log&quot;
	&quot;time&quot;

	&quot;golang.org/x/time/rate&quot;
)

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 &lt;-ctx.Done():
				log.Printf(&quot;finished!&quot;)
				return
			case msg := &lt;-msgs:
				log.Printf(&quot;receiving a message: %s&quot;, msg)
			}
		}
	}(ctx, limiter)

	defer close(msgs)

	i := 0
	for {
		msgs &lt;- fmt.Sprintf(&quot;sending message %d&quot;, i)
		i++
		if i &gt; 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 &lt;- fmt.Sprintf(&quot;sending message %d&quot;, 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 (
	&quot;context&quot;
	&quot;fmt&quot;
	&quot;log&quot;
	&quot;time&quot;

	&quot;golang.org/x/time/rate&quot;
)

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 &lt;-ctx.Done():
				log.Printf(&quot;finished!&quot;)
				return
			case msg := &lt;-msgs:
				log.Printf(&quot;receiving a message: %s&quot;, msg)
			}
		}
	}()

	defer close(msgs)

	for i := 0; i &lt; 100000; i++ {
		select {
		case msgs &lt;- fmt.Sprintf(&quot;sending message %d&quot;, i):
		case &lt;-ctx.Done():
			log.Printf(&quot;finished too!&quot;)
			return
		}
	}
}

答案2

得分: 1

使用<-发送或接收消息是一个阻塞操作。当上下文超时结束时,goroutine已经退出,调用者无法继续执行。

// Goroutine已经结束,这个调用永远不会结束
msgs <- fmt.Sprintf("发送消息 %d", i)

此时程序发生了死锁。

至于非确定性,goroutine和主执行上下文并发运行。由于有两个没有太多延迟的for循环,线程彼此竞争。不能保证它们每次都以相同的方式执行。

英文:

Sending or receiving a message using &lt;- 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 &lt;- fmt.Sprintf(&quot;sending message %d&quot;, 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.

huangapple
  • 本文由 发表于 2021年12月31日 15:17:58
  • 转载请务必保留本文链接:https://go.coder-hub.com/70539909.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定