登录
首页 >  Golang >  Go问答

“不要传输数据,而是转移数据所有权”

来源:stackoverflow

时间:2024-03-12 18:18:27 166浏览 收藏

哈喽!今天心血来潮给大家带来了《“不要传输数据,而是转移数据所有权”》,想必大家应该对Golang都不陌生吧,那么阅读本文就都不会很困难,以下内容主要涉及到,若是你正在学习Golang,千万别错过这篇文章~希望能帮助到你!

问题内容

我了解到 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

正如您所提到的,这里的潜在问题是您正在移动大量数据,因此您可能会进行过多的内存复制。

您可以通过发送引用类型来防止这种情况,例如通道上的指针或切片:

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[:]

所以我们只是进行了完全相同的数据传输,但减少了内存复制,对吧?好吧,这里有一个潜在的问题:您通过通道发送了对 data 的引用,但是即使在发送之后,data 值仍然可以在当前范围内访问:

// write a reference to data to the channel
c <- data[:]

// start messing with data
data[0] = 999
data[1] = 1234
...

这段代码可能刚刚引入了潜在的数据竞争,因为无论谁从通道读取该切片,都可能在您开始修改它的同时对其进行处理。

传递所有权的想法是,在您给出对某物的引用后,您也承认了对该事物的所有权,并且不会使用它。只要我们在给出引用(在通道上发送切片)之后不使用 data,那么我们就已经正确地传递了所有权。

这个问题是共享状态一般问题的延伸。例如,与 rust 不同的是,go 没有语言结构来正确控制共享状态。为了减少出现这些错误的机会,您可以应用一些策略:

  • 避免在通道上传递引用:在上面的示例中,一旦我们开始使用切片通过引用传递数据,就会出现问题。这样做只是为了减少内存应对量。除非有实际原因进行此优化(测量了有价值的性能差异),否则可以完全避免。尽管如此,go 中仍有一些数据类型本质上是引用(例如映射和切片)。如果这些类型必须在通道上传递,则可以使用其他策略。
  • 将数据创建逻辑分离为函数:在上面的示例中,我们可以重构代码:
func senddata(c chan []int) {
    var data [1000]int
    // populate the data

    // write a reference to data to the channel
    c <- data[:]
}
c := make(chan []int)

// spawn some goroutines that read from this channel

// send some data
sendData(c)

错误使用 data 的可能性仍然存在,但现在它被隔离为一个具有明确意图的小函数。理论上,隔离应该使代码更容易理解,更明显 data 的正确用法是什么,并且更少的更改会与其产生潜在的交互。

  • 不要将数据管道与持久状态混合:数据管道是指两个或多个并发例程,数据通过通道在这些例程之间流动。扩展上一点,使自有引用的创建尽可能靠近它们进入数据管道的位置。在 goroutine 接收数据的位置和再次发送数据或使用数据的位置之间留出尽可能紧密的空间。在所有权的一般规则中,只有当您目前拥有某物的完全所有权时,您才能转让该物的所有权。根据此规则,您应尽可能避免在并非发送前刚刚创建引用数据的频道上发送任何引用。如果您引用任何持久或全局状态,那么确保尊重所有权就会变得更加困难。

通过将引用的创建和所有权的转移保持在一个隔离的全局函数中,应该更难犯错误。那么违反所有权规则的唯一方法是:

  1. 泄露对全局状态的引用
  • 尝试消除全局变量和全局状态
  1. 泄漏对引用类型参数状态的引用
  • 不要在数据发送函数中使用任何引用类型参数
  1. 发送参考后修改参考数据
  • 将发送操作放在函数的最后。如有必要,您可以将发送放入延迟调用中。

没有完美的解决方案来消除所有共享状态问题(即使在 rust 中,它们在实践中有时也存在),但我希望这些策略能够帮助您思考如何解决这个问题。

Hymns For Disco's answer 很好,但我发现写出好的简短有时会回答一个有趣的挑战,我想我有一个类比在这里会有所帮助。

将您的数据想象成占据仓库,每个仓库的大小都相当于一个大型城市街区。您有一千个仓库,分布在许多国家和城市。

你们有五名技术精湛的技术人员,每个人都能把一件事做得非常好。您需要所有技术人员来操作所有数据。不幸的是,每个技术人员都讨厌其他四个人,如果他们中的任何一个出现在仓库中,他们就不会做任何工作(甚至可能会试图杀死其他人)。

解决这个问题的一种方法是建造五个额外的仓库,并将五名技术人员分别安置在新的五个仓库中。然后,您可以将 1000 个仓库中每个仓库的全部内容运送到各个备件,一次一个,然后在每位技术人员完成处理后将内容移回;您可以通过将内容移动到工作仓库 #1、然后到 #2、然后到 #3 等来优化仓库内容移动,并且仅在准备离开后才将其移回原始仓库#5。但显然,这需要大量的运输和物流,并且需要大量的时间和金钱来完成所有这些批量运输。即使每个技术人员可以在一天之内完全处理整个仓库,也需要数年时间和大量资金才能完成所有工作。

或者,您可以派遣五名技术人员。将它们发送到仓库 (wh) 1-5。当 tech#1 处理完 wh#1 后,将他移动到 wh#2,除非 tech#2 仍然在那里;如果这是下一个免费的,请将他移动到 wh#6。

我们正在移动小而轻的“做工作的人”,而不是大而重的“占据该人工作空间的东西”。总体成本要低得多。不过,我们必须小心,不要意外地让技术人员相遇。

还要注意,如果数据本身又小又轻且易于移动,那么这种花哨的解决方案(即仔细考虑谁在何时可以访问哪些数据)就无济于事。在小数据情况下,我们不妨移动数据,而不是工人。

今天关于《“不要传输数据,而是转移数据所有权”》的内容介绍就到此结束,如果有什么疑问或者建议,可以在golang学习网公众号下多多回复交流;文中若有不正之处,也希望回复留言以告知!

声明:本文转载于:stackoverflow 如有侵犯,请联系study_golang@163.com删除
相关阅读
更多>
最新阅读
更多>
课程推荐
更多>