我了解到 Golang 通道实际上比该语言提供的许多替代方案慢。当然,它们确实很容易掌握,但由于它们是高级结构,因此会带来一些开销。
阅读了一些有关它的文章,我发现有人在这里对渠道进行基准测试。他基本上说通道可以传输 10 MB/s,这当然必须取决于他的硬件。然后他说了一些我没完全听懂的话:
如果您只想使用通道快速移动数据,那么一次移动 1 个字节是不明智的。您对通道真正要做的就是移动数据的所有权,在这种情况下,数据速率实际上可以是无限的,具体取决于您传输的数据块的大小。
我在几个地方看到过这种“移动数据所有权”,但我还没有看到一个可靠的例子来说明如何做到这一点,而不是移动数据本身。
我想看一个例子来理解这个最佳实践。
通过通道移动数据:
c := make(chan [1000]int)
// spawn some goroutines that read from this channel
var data [1000]int
// populate the data
// write data to the channel
c <- data
Run Code Online (Sandbox Code Playgroud)
正如您所提到的,这里的潜在问题是您正在移动大量数据,因此您可能会进行过多的内存复制。
您可以通过发送引用类型(例如通道上的指针或切片)来防止这种情况:
c := make(chan []int)
// spawn some goroutines that read from this channel
var data [1000]int
// populate the data
// write a reference to data to the channel
c <- data[:]
Run Code Online (Sandbox Code Playgroud)
所以我们只是进行了完全相同的数据传输,但减少了内存复制,对吧?好吧,这里有一个潜在的问题:您通过通道发送了对 的引用data,但data即使在发送之后,该值仍然可以在当前作用域中访问:
// write a reference to data to the channel
c <- data[:]
// start messing with data
data[0] = 999
data[1] = 1234
...
Run Code Online (Sandbox Code Playgroud)
此代码可能刚刚引入了潜在的数据竞争,因为从通道读取该切片的任何人都可能在您开始修改它的同时对其进行处理。
传递所有权的想法是,在您给出对某物的引用后,您也放弃了对该事物的所有权,并且不会使用它。只要我们data在给出引用后不使用(在通道上发送切片),那么我们就已经正确地传递了所有权。
这个问题是共享状态一般问题的延伸。例如,与 Rust 不同的是,Go 没有语言结构来正确控制共享状态。为了减少出现这些错误的机会,您可以应用一些策略:
func sendData(c chan []int) {
var data [1000]int
// populate the data
// write a reference to data to the channel
c <- data[:]
}
Run Code Online (Sandbox Code Playgroud)
c := make(chan []int)
// spawn some goroutines that read from this channel
// send some data
sendData(c)
Run Code Online (Sandbox Code Playgroud)
错误使用的可能性data仍然存在,但现在它被隔离为一个具有明确意图的小函数。理论上,隔离应该使代码更容易理解,更明显正确的用法是什么data,并且更少的更改会与其产生潜在的交互。
通过将引用的创建和所有权的转移保持在一个孤立的全局函数中,应该更难犯错误。那么违反所有权规则的唯一方法是:
没有完美的解决方案来消除所有共享状态问题(即使在 Rust 中,它们在实践中有时也存在),但我希望这些策略将帮助您思考如何解决这个问题。
| 归档时间: |
|
| 查看次数: |
635 次 |
| 最近记录: |