为了账号安全,请及时绑定邮箱和手机立即绑定

“不要通过通道移动数据,将数据所有权移动到通道”的含义

“不要通过通道移动数据,将数据所有权移动到通道”的含义

Go
白猪掌柜的 2022-09-05 17:20:36
我正在学习Golang频道实际上比该语言提供的许多替代方案慢。当然,它们确实很容易掌握,但是因为它们是一个高级结构,所以它们会带来一些开销。阅读有关它的一些文章,我发现有人在这里对频道进行基准测试。他基本上说通道可以传输10 MB / s,这当然必须取决于他的硬件。然后他说了一些我不完全理解的话:如果您只想使用通道快速移动数据,那么一次移动1个字节是不明智的。您真正要做的是移动数据的所有权,在这种情况下,数据速率实际上可能是无限的,具体取决于您传输的数据块的大小。我已经在几个地方看到过这种“移动数据所有权”,但我还没有看到一个可靠的例子来说明如何做到这一点,而不是移动数据本身。我想看一个例子来理解这个最佳实践。
查看完整描述

2 回答

?
阿晨1998

TA贡献2037条经验 获得超6个赞

通过通道移动数据:

c := make(chan [1000]int)// spawn some goroutines that read from this channelvar data [1000]int// populate the data// write data to the channelc <- data

正如你所提到的,这里的潜在问题是你正在移动大量数据,所以你可能正在做过多的内存复制。

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

c := make(chan []int)// spawn some goroutines that read from this channelvar data [1000]int// populate the data// write a reference to data to the channelc <- data[:]

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

// write a reference to data to the channelc <- data[:]// start messing with datadata[0] = 999data[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 datasendData(c)

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

  • 不要将数据管道与持久状态混合:通过数据管道,我指的是两个或多个并发例程,其中数据通过通道流动。在上一点的基础上进行扩展,使拥有的引用的创建尽可能接近它们进入数据管道的位置。在 goroutine 接收数据的位置和再次发送数据或使用数据的位置之间留出尽可能紧凑的空间。在所有权的一般规则中,您只能在目前拥有完全所有权的情况下转让某物的所有权。由于此规则,应尽可能避免在通道上发送任何引用,而不仅仅是在发送前立即创建引用的数据。如果您引用了任何持久性或全局状态,则确保尊重所有权将变得更加困难。

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

  1. 泄漏对全局状态的引用

  • 尝试消除全局变量和全局状态

  1. 泄漏对引用类型参数状态的引用

  • 不要在数据发送函数中采用任何引用类型参数

  1. 发送参考后修改参考数据

  • 将发送操作放在函数的最末尾。如有必要,您可以将发送放在延迟的呼叫中。

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


查看完整回答
反对 回复 2022-09-05
?
呼啦一阵风

TA贡献1802条经验 获得超6个赞

我发现写出好的简短答案有时是一个有趣的挑战,我想我有一个类比可以在这里有所帮助。

将您的数据想象成占用仓库,每个仓库的大小都是一个大的城市街区。你有一千个仓库,分散在许多国家和城市。

你有五名高技能的技术人员,他们每个人都可以把一件事做得很好。您需要所有技术人员对所有数据进行操作。不幸的是,每个技术人员都讨厌其他四个人,如果仓库里有他们中的任何一个,他们就不会做任何工作(甚至可能试图杀死其他人)。

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

或者,您可以运送五名技术人员。将它们发送到仓库(WHs)1-5。当技术#1完成WH#1时,除非技术#2仍然存在,否则将他移动到WH#2;将他移动到WH#6,如果这是下一个免费的人。

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

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


查看完整回答
反对 回复 2022-09-05
  • 2 回答
  • 0 关注
  • 51 浏览
慕课专栏
更多

添加回答

举报

0/150
提交
取消
意见反馈 帮助中心 APP下载
官方微信