为什么方向通道的切片无法编译?

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

Why are slices of directional channels failing to compile?

问题

以下是翻译好的内容:

上面的代码片段出现以下错误:

./prog.go:28:6: 无法将 ch (类型为 []chan int) 作为 read 函数的参数类型 []<-chan int 使用
./prog.go:29:7: 无法将 ch (类型为 []chan int) 作为 write 函数的参数类型 []chan<- int 使用

如何在 readwrite 函数中使用通道方向使其正常工作?

英文:

https://play.golang.org/p/-cHBgiNl0tK

package main

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

func read(ch ...&lt;-chan int) {
	for i := 0; i &lt; len(ch); i++ {
		i := i
		go func() { fmt.Println(&lt;-ch[i]) }()
	}
}

func write(ch ...chan&lt;- int) {
	for i := 0; i &lt; len(ch); i++ {
		i := i
		go func() { ch[i] &lt;- i }()
	}
}

func main() {
	var maxLen = 10
	var ch []chan int
	for i := 0; i &lt; maxLen; i++ {
		ch = append(ch, make(chan int))
	}
	read(ch...)
	write(ch...)
	time.Sleep(10 * time.Second)
}

The above snippet is failing with below error

./prog.go:28:6: cannot use ch (type []chan int) as type []&lt;-chan int in argument to read
./prog.go:29:7: cannot use ch (type []chan int) as type []chan&lt;- int in argument to write

How can I make it work with channel directions in read and write functions?

答案1

得分: 4

你正在尝试将[]chan int转换为[]<-chan int。看起来像是一个简单的转换,但实际上这是一个深度转换,因为你正在改变容器的元素类型。在Go语言中,这种转换是不允许的,尽管有一个已存在的(已关闭的)提案,允许进行这种类型的通道切片转换:https://github.com/golang/go/issues/41695

以下是我过去在类似情况下使用的解决方案:

func readOnlyChannels(slice []chan int) []<-chan int {
	return *(*[]<-chan int)(unsafe.Pointer(&slice))
}

func writeOnlyChannels(slice []chan int) []chan<- int {
	return *(*[]chan<- int)(unsafe.Pointer(&slice))
}

然后使用方式:

read(readOnlyChannels(ch)...)
write(writeOnlyChannels(ch)...)

由于通道的只读或只写属性仅在编译时的类型信息中存在,并不在类型的实际数据表示中**[需要引用]**,你可以使用unsafe来强制进行转换。


注意:我不知道是否有任何明确或隐含的保证unsafe转换是有效的,所以你应该创建测试来验证它在目标平台上是否有效。然而,在实践中,它似乎是有效的。它依赖于不同方向的通道值具有相同(或至少兼容)的数据表示。从实际角度来看,通道方向性的设计没有理由要求或甚至从在运行时保持通道方向性中获益。

如果你想完全避免使用unsafe,那么你将不得不进行"长"转换,像这样:

func readOnlyChannels(slice []chan int) []<-chan int {
	out := make([]<-chan int, len(slice))
	for i := range slice {
		out[i] = slice[i]
	}
	return out
}

func writeOnlyChannels(slice []chan int) []chan<- int {
	out := make([]chan<- int, len(slice))
	for i := range slice {
		out[i] = slice[i]
	}
	return out
}

这里唯一的真正缺点是,在进行转换时,它将通过将所有内容复制到一个新的切片中导致更多的分配。在你的应用程序中使用最不会引起问题的方法。

英文:

You're trying to pass a []chan int as a []&lt;-chan int. Seems like a simple conversion, but it's actually a deep conversion as you're changing the element type of the container. These conversions aren't allowed in Go, though there is an existing (closed) proposal to make this kind of channel slice conversion allowed: https://github.com/golang/go/issues/41695

Here's a solution I've used in a similar situation in the past:

func readOnlyChannels(slice []chan int) []&lt;-chan int {
	return *(*[]&lt;-chan int)(unsafe.Pointer(&amp;slice))
}

func writeOnlyChannels(slice []chan int) []chan&lt;- int {
	return *(*[]chan&lt;- int)(unsafe.Pointer(&amp;slice))
}

And then usage:

	read(readOnlyChannels(ch)...)
	write(writeOnlyChannels(ch)...)

Since the read-only or write-only quality of a channel is only carried in the type information at compile time, and not in the actual data representation of the type [citation needed], you can use unsafe to just force a conversion.


Note: I'm not aware of any explicit or implicit guarantee that the unsafe conversion is valid, so you should create tests to verify that this works on your target platforms. However, in practice, it seems to work. It relies on differently-directioned channel values having identical (or at least compatible) data representations. Practically speaking, there's no reason why the design of channel directionality would require or even benefit from maintaining the channel direction at run-time.

If you want to avoid unsafe completely, then you'll have to do the "long" conversion like this:

func readOnlyChannels(slice []chan int) []&lt;-chan int {
	out := make([]&lt;-chan int, len(slice))
	for i := range slice {
		out[i] = slice[i]
	}
	return out
}

func writeOnlyChannels(slice []chan int) []chan&lt;- int {
	out := make([]chan&lt;- int, len(slice))
	for i := range slice {
		out[i] = slice[i]
	}
	return out
}

The only real downside here is that it's going to cause more allocations by copying everything into a new slice when you do the conversion. Use whichever approach poses the least issue in your application.

答案2

得分: 3

尽管类型为chan int的表达式可以分配给类型为chan<- int(只发送通道)或<-chan int(只接收通道)的变量,但对于这些类型的切片却不适用:

var (
    ch chan int
    _  <-chan int   = ch // 可行
    _  chan<- int   = ch // 可行
    _  []<-chan int = []chan int(nil) // 编译错误
    _  []chan<- int = []chan int(nil) // 编译错误
)

(Playground)

./prog.go:7:2: cannot use ([]chan int)(nil) (type []chan int) as type []<-chan int in assignment
./prog.go:8:2: cannot use ([]chan int)(nil) (type []chan int) as type []chan<- int in assignment

正如Hymns for Disco在他/她的回答中指出的,不幸的是没有简单的解决方法。我不建议使用unsafe包,因为unsafe的风险有点抵消了使用通道方向的好处。我只会在函数writeread的参数中省略通道方向。具体情况可能有所不同。

英文:

Although an expression of type chan int is assignable to a variable of type either chan&lt;- int (send-only channel) or &lt;-chan int (receive-only channel), the same doesn't apply to slices of such types:

var (
	ch chan int
	_  &lt;-chan int   = ch // ok
	_  chan&lt;- int   = ch // ok
	_  []&lt;-chan int = []chan int(nil) // compilation error
	_  []chan&lt;- int = []chan int(nil) // compilation error
)

(Playground)

./prog.go:7:2: cannot use ([]chan int)(nil) (type []chan int) as type []&lt;-chan int in assignment
./prog.go:8:2: cannot use ([]chan int)(nil) (type []chan int) as type []chan&lt;- int in assignment

As pointed out by Hymns for Disco in his/her answer, there is unfortunately no easy way out. I wouldn't recommend resorting to the unsafe package myself, because the risks associated with unsafe kind of offset the benefits of using channel directions. I would simply drop the channel directions in the parameters to functions write and read. YMMV.

答案3

得分: 0

使用支持方向通道的通道的替代方法。

package main

import (
	"fmt"
	"time"
)

func read(inputs chan (<-chan int)) {
	for input := range inputs {
		go func(input <-chan int) { fmt.Println(<-input) }(input)
	}
}

func write(outputs chan chan<- int) {
	i := 0
	for output := range outputs {
		go func(output chan<- int, i int) { output <- i }(output, i)
		i += 1
	}
}

func main() {
	var maxLen = 10
	readers := make(chan (<-chan int), 10)
	writers := make(chan chan<- int, 10)
	for i := 0; i < maxLen; i++ {
		ch := make(chan int)
		readers <- ch
		writers <- ch
	}
	close(readers)
	close(writers)
	read(readers)
	write(writers)
	time.Sleep(10 * time.Second)
}
英文:

Alternative approach with channel of channels with support for directional channels.

package main

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

func read(inputs chan (&lt;-chan int)) {
	for input := range inputs {
		go func(input &lt;-chan int) { fmt.Println(&lt;-input) }(input)
	}
}

func write(outputs chan chan&lt;- int) {
	i := 0
	for output := range outputs {
		go func(output chan&lt;- int, i int) { output &lt;- i }(output, i)
		i += 1
	}
}

func main() {
	var maxLen = 10
	readers := make(chan (&lt;-chan int), 10)
	writers := make(chan chan&lt;- int, 10)
	for i := 0; i &lt; maxLen; i++ {
		ch := make(chan int)
		readers &lt;- ch
		writers &lt;- ch
	}
	close(readers)
	close(writers)
	read(readers)
	write(writers)
	time.Sleep(10 * time.Second)
}

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

发表评论

匿名网友

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

确定