当发生 panic(恐慌)时,为什么发送到通道会被阻塞?

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

Why does sending to channel is blocked when a panic occurs?

问题

我将为您翻译以下代码段:

package main

import (
	"context"
	"fmt"
	"time"
)

type Loop struct {
	done chan struct{}
}

type Result struct {
	ID string
}

func NewLoop() *Loop {
	return &Loop{
		done: make(chan struct{}),
	}
}

func (l *Loop) StartAsync(ctx context.Context) {
	go func() {
		defer func() {
			l.done <- struct{}{} // BLOCKED! But I allocated it in NewLoop ctor
			fmt.Sprintf("done")
		}()
		for {
			/**/
			var data *Result
			l.processData(ctx, data) // passed nil
		}
	}()
}

func (l *Loop) processData(ctx context.Context, data *Result) {
	_ = fmt.Sprintf("%s", data.ID) // intentional panic - OK
}
func main() {
	l := NewLoop()
	l.StartAsync(context.Background())
	time.Sleep(10 * time.Second)
}

我可以在发送到通道之前使用recover()来恢复 panic,并获得正确的错误消息。
通道发生了什么?如果它被关闭,我将会在发送到已关闭的通道时发生 panic。

英文:

I am going to show a simple compilable code snipped where I get weird behaviour: after I intentionally cause a panic in processData (because I pass nil-pointer) the sending to channel l.done is blocked!

package main

import (
	&quot;context&quot;
	&quot;fmt&quot;
	&quot;time&quot;
)

type Loop struct {
	done chan struct{}
}

type Result struct {
	ID string
}

func NewLoop() *Loop {
	return &amp;Loop{
		done: make(chan struct{}),
	}
}

func (l *Loop) StartAsync(ctx context.Context) {
	go func() {
		defer func() {
			l.done &lt;- struct{}{} // BLOCKED! But I allocated it in NewLoop ctor
			fmt.Sprintf(&quot;done&quot;)
		}()
		for {
			/**/
			var data *Result
			l.processData(ctx, data) // passed nil
		}
	}()
}

func (l *Loop) processData(ctx context.Context, data *Result) {
	_ = fmt.Sprintf(&quot;%s&quot;, data.ID) // intentional panic - OK
}
func main() {
	l := NewLoop()
	l.StartAsync(context.Background())
	time.Sleep(10 * time.Second)
}

I can recover() a panic before sending to channel and I get correct error message.
What does happen with channel? It it was closed, I would get panic on sending to closed channel

答案1

得分: 1

这是要翻译的内容:

由于通道中没有接收任何内容,所以它是阻塞的。对于已初始化且无缓冲的通道,接收和发送操作都会无限期地阻塞,如果缺少相反的操作。也就是说,向通道发送数据将会阻塞,直到另一个goroutine从该通道接收数据;同样地,从通道接收数据将会阻塞,直到另一个goroutine向该通道发送数据。

所以基本上,

done := make(chan struct{})
done <- struct{}{}

将会永远阻塞,除非有另一个goroutine从通道接收数据,即<-done。这就是通道应该的行为方式。这也是语言规范定义它们行为的方式。

英文:

It's blocking because there isn't anything receiving from the channel. Both the receive & the send operations on an initialized and unbuffered channel will block indefinitely if the opposite operation is missing. I.e. a send to a channel will block until another goroutine receives from that channel, and, likewise, a receive from a channel will block until another goroutine sends to that channel.

So basically

done := new(chan struct{})
done&lt;-struct{}{}

will block forever unless there is another goroutine that receives from the channel, i.e. &lt;-done. That's how channels are supposed to behave. That's how the languages specification defines their behaviour.

答案2

得分: 1

关于可能的修复方法:

根据你的频道名称,你可能想要运行close(l.done)而不是l.done <- struct{}{}

使用带缓冲的通道和l.done <- struct{}{}在完成时:只有一个<-l.done的调用将被解除阻塞。

假设你有一些类似下面的代码:

l := NewLoop()

go func(){
    <-l.done
    closeLoggers()
}()

go func(){
    <-l.done
    closeDatabase()
}()

done通道上发送一个项目将使得只有一个消费者会接收到它,在上面的示例中,当循环完成时,只会触发两个动作中的一个。

使用close(l.done):一旦通道关闭,所有对它的接收调用都会继续执行。

在上面的示例中,所有的动作都会继续执行。

顺便提一下:如果你只使用通道的“打开/关闭”状态,那么就不需要缓冲区。

英文:

about the possible fixes :

given the name of your channel, you may want to run close(l.done) rather than l.done &lt;- struct{}{}.

  • using a buffered channel and l.done &lt;- struct{}{} on completion : only one call to &lt;-l.done will be unblocked.

Suppose you have some code looking like :

l := NewLoop()

go func(){
    &lt;-l.done
    closeLoggers()
}()

go func(){
    &lt;-l.done
    closeDatabase()
}()

sending one item on the done channel will make that only one consumer will receive it, and in the above example only one of the two actions will be triggered when the loop completes.

  • using close(l.done) : once a channel is closed all calls to receive from it will proceed.

In the above example, all actions will proceed.

As a side note: there is no need for a buffer if you use a channel only for its "open/closed" state.

huangapple
  • 本文由 发表于 2022年8月26日 02:24:17
  • 转载请务必保留本文链接:https://go.coder-hub.com/73492149.html
匿名

发表评论

匿名网友

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

确定