何时使用终结器(finalizer)来关闭通道?

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

When to use a finilizer to close a channel?

问题

这是两个问题中的第二个问题(第一个问题在此处:链接),旨在帮助理解Go泛型提案的示例部分。

特别是,我对提案中名为“Channels”的示例部分中的两段代码有困惑。

我对以下Ranger函数的定义有疑问。

我不明白为什么需要调用runtime.SetFinalizer(r, r.finalize),实际上*Receiver[T]类型的finalize方法只是用来表示接收器已经完成接收值的信号(close(r.done))。

我理解的方式是,通过为*Receiver[T]提供一个finalizer,代码将关闭接收器的责任委托给了运行时。

我对这段代码的理解是,当GC确定*Receiver[T]不可达时,即没有更多的引用可用时,*Receiver[T]*Sender[T]发出不再接收任何值的信号。

如果我的理解是正确的,为什么要等到接收器发出完成信号才行呢?难道不可能在代码中显式处理close操作吗?

谢谢。

代码:

// Ranger提供了一种方便的方法,当接收器停止读取值时,退出一个发送值的goroutine。
//
// Ranger返回一个Sender和一个Receiver。Receiver提供了一个Next方法来检索值。Sender提供了一个Send方法来发送值,以及一个Close方法来停止发送值。Next方法指示Sender何时被关闭,Send方法指示Receiver何时被释放。
func Ranger[T any]() (*Sender[T], *Receiver[T]) {
	c := make(chan T)
	d := make(chan bool)
	s := &Sender[T]{values: c, done: d}
	r := &Receiver[T]{values: c, done: d}
	// 接收器的finalizer将告诉发送器接收器是否停止监听。
	runtime.SetFinalizer(r, r.finalize)
	return s, r
}

// Sender用于向Receiver发送值。
type Sender[T any] struct {
	values chan<- T
	done   <-chan bool
}

// Send向接收器发送一个值。它报告是否可以发送更多值;如果返回false,则该值未发送。
func (s *Sender[T]) Send(v T) bool {
	select {
	case s.values <- v:
		return true
	case <-s.done:
		// 接收器已停止监听。
		return false
	}
}

// Close告诉接收器不会再有更多的值到达。
// 调用Close后,Sender将不能再使用。
func (s *Sender[T]) Close() {
	close(s.values)
}

// Receiver从Sender接收值。
type Receiver[T any] struct {
	values <-chan T
	done   chan<- bool
}

// Next从通道中返回下一个值。bool结果报告该值是否有效。如果该值无效,则表示Sender已关闭,将不再接收到更多值。
func (r *Receiver[T]) Next() (T, bool) {
	v, ok := <-r.values
	return v, ok
}

// finalize是接收器的finalizer。
// 它告诉发送器接收器已停止监听。
func (r *Receiver[T]) finalize() {
	close(r.done)
}
英文:

This is the second of two questions (this is the first one) to help make sense of the Go generics proposal examples.

In particular I am having trouble-so far-understanding two bits of code from the examples section of the proposal entitled "Channels":

The second issue I have is in the following definition of the the Ranger function.

Namely, I don't understand the need to call runtime.SetFinalizer(r,r.finalize) where in fact what the finalize) method of the *Receiver[T] type is supposed to do is simply to signal that the receiver is done receiving values (close(r.done)).

The way I see it, by providing a finalizer for a *Receiver[T] the code is delegating the obligation to close the receiver to the runtime.

The way I understand this piece of code, is that the *Receiver[T] signals to the *Sender[T] that it won't be receiving any more values when the GC decides that the former is unreachable ie no more references are available to it.

If my interpretation is correct, why wait that long for the receiver to signal it's done? Is't it possible, to explicitly handle the close operation in the code somehow?

Thanks.

Code:

// Ranger provides a convenient way to exit a goroutine sending values
// when the receiver stops reading them.
//
// Ranger returns a Sender and a Receiver. The Receiver provides a
// Next method to retrieve values. The Sender provides a Send method
// to send values and a Close method to stop sending values. The Next
// method indicates when the Sender has been closed, and the Send
// method indicates when the Receiver has been freed.
func Ranger[T any]() (*Sender[T], *Receiver[T]) {
c := make(chan T)
d := make(chan bool)
s := &amp;Sender[T]{values: c, done: d}
r := &amp;Receiver[T]{values: c, done: d}
// The finalizer on the receiver will tell the sender
// if the receiver stops listening.
runtime.SetFinalizer(r, r.finalize)
return s, r
}
// A Sender is used to send values to a Receiver.
type Sender[T any] struct {
values chan&lt;- T
done   &lt;-chan bool
}
// Send sends a value to the receiver. It reports whether any more
// values may be sent; if it returns false the value was not sent.
func (s *Sender[T]) Send(v T) bool {
select {
case s.values &lt;- v:
return true
case &lt;-s.done:
// The receiver has stopped listening.
return false
}
}
// Close tells the receiver that no more values will arrive.
// After Close is called, the Sender may no longer be used.
func (s *Sender[T]) Close() {
close(s.values)
}
// A Receiver receives values from a Sender.
type Receiver[T any] struct {
values &lt;-chan T
done  chan&lt;- bool
}
// Next returns the next value from the channel. The bool result
// reports whether the value is valid. If the value is not valid, the
// Sender has been closed and no more values will be received.
func (r *Receiver[T]) Next() (T, bool) {
v, ok := &lt;-r.values
return v, ok
}
// finalize is a finalizer for the receiver.
// It tells the sender that the receiver has stopped listening.
func (r *Receiver[T]) finalize() {
close(r.done)
}

答案1

得分: 1

TLDR: 你的理解是正确的,done通道可以被接收方手动关闭,以表示失去兴趣(停止通信并解除发送方的责任)。


通道用于在并发安全的方式下进行goroutine之间的通信。惯用的用法是发送方持续发送值,一旦没有更多的值要发送,发送方通过关闭通道来发出信号。

接收方会持续从通道接收值,直到通道关闭,这表示不会再有(也不能有)更多的值传入通道。通常情况下,可以使用for range循环来实现这一点。

因此,通常情况下,接收方必须持续接收值,直到通道关闭,否则发送方将永远被阻塞。通常情况下,这是可以接受的/足够的。

Ranger()的示例是用于非一般情况,当接收方需要/可能需要停止通信时。

单个通道无法提供一种方式让接收方向发送方发出信号,表示接收方失去兴趣,不再需要更多的值。这需要一个额外的通道,接收方必须关闭该通道(当然发送方也必须监视该通道)。只要有一个接收方,这也是可以接受的。但是,如果有多个接收方,关闭done通道就会变得更加复杂:不是所有接收方都可以关闭done通道,关闭已经关闭的通道会导致恐慌。因此,接收方还必须进行协调,只有一个接收方,或者更准确地说是协调方本身关闭done通道,只能发生一次;而且这必须在所有接收方都“放弃”通道之后发生。

Ranger()通过委托使用终结器来帮助解决这个问题,以关闭done通道。这是可以接受的,因为通常甚至不需要接收方来停止通信,但是在罕见的情况下,如果确实需要停止通信,可以轻松处理(无需额外的协调goroutine)。

英文:

TLDR: Your understanding is correct, the done channel may simply be closed by the receiver "manually" to signal the lost of interest (to stop the communication and relieve the sender from its duty).


Channels are used for goroutines to communicate in a concurrency safe manner. The idiomatic use is that the sender party keeps sending values, and once there are no more values to send, it is signaled by the sender closing the channel.

The receiver party keeps receiving from the channel until it is closed, which signals there won't be (can't be) any more values coming on the channel. This is usually / easiest done using a for range over the channel.

So usually the receiver has to keep receiving until the channel is closed, else the sender party would get blocked forever. Often this is OK / sufficient.

The demonstrated Ranger() construct is for the non-general case when there's need / possibility for the receiver to stop the communication.

A single channel does not provide a mean for the receiver party to signal the sender that the receiver has lost interest, and no more values are needed. This requires an additional channel which the receiver has to close (and the sender has to monitor of course). As long as there's a single receiver, this is also OK. But if there are multiple receivers, closing the done channel gets a little more complicated: it's not OK for all the receivers to close the done channel: closing an already closed channel panics. So the receivers also have to be coordinated, so only a single receiver, or rather the coordinator party itself closes the done channel, once only; and this has to happen after all receivers "abandoned" the channel.

Ranger() helps with this, and in a simple way by delegating closing the done channel using a finalizer. This is acceptable because usually it wouldn't even be the receiver(s) task to stop the communication, but in the rare case if this still arises, it will be dealt with (in an easy way, without the need of an additional, coordinator goroutine).

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

发表评论

匿名网友

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

确定