Is a struct actually copied between goroutines if sent over a Golang channel?

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

Is a struct actually copied between goroutines if sent over a Golang channel?

问题

在Go语言中,如果通过通道传输一个大的结构体,它是否会在goroutine之间进行实际的复制呢?

例如,在下面的代码中,Go语言是否会在生产者和消费者之间实际复制所有的largeStruct数据?

  1. package main
  2. import (
  3. "fmt"
  4. "sync"
  5. )
  6. type largeStruct struct {
  7. buf [10000]int
  8. }
  9. func main() {
  10. ch := make(chan largeStruct)
  11. wg := &sync.WaitGroup{}
  12. wg.Add(2)
  13. go consumer(wg, ch)
  14. go producer(wg, ch)
  15. wg.Wait()
  16. }
  17. func producer(wg *sync.WaitGroup, output chan<- largeStruct) {
  18. defer wg.Done()
  19. for i := 0; i < 5; i++ {
  20. fmt.Printf("producer: %d\n", i)
  21. output <- largeStruct{}
  22. }
  23. close(output)
  24. }
  25. func consumer(wg *sync.WaitGroup, input <-chan largeStruct) {
  26. defer wg.Done()
  27. i := 0
  28. LOOP:
  29. for {
  30. select {
  31. case _, ok := <-input:
  32. if !ok {
  33. break LOOP
  34. }
  35. fmt.Printf("consumer: %d\n", i)
  36. i++
  37. }
  38. }
  39. }

在Go语言中,通过通道传输的数据是通过复制的方式进行传递的。在这个例子中,largeStruct数据会在生产者和消费者之间进行复制。每次生产者发送一个largeStruct实例到通道时,消费者会从通道接收到一个复制的largeStruct实例。因此,每个goroutine都会拥有自己的largeStruct实例的副本。

请注意,这种复制是浅复制,只复制结构体的值,而不会复制指向的底层数据。因此,复制的largeStruct实例仍然共享相同的底层数据。

英文:

If a large struct is sent over a channel in Go, is it actually copied between goroutines?

For example, in the code below, will Go actually copy all largeStruct data between goroutines producer and consumer?

  1. package main
  2. import (
  3. &quot;fmt&quot;
  4. &quot;sync&quot;
  5. )
  6. type largeStruct struct {
  7. buf [10000]int
  8. }
  9. func main() {
  10. ch := make(chan largeStruct)
  11. wg := &amp;sync.WaitGroup{}
  12. wg.Add(2)
  13. go consumer(wg, ch)
  14. go producer(wg, ch)
  15. wg.Wait()
  16. }
  17. func producer(wg *sync.WaitGroup, output chan&lt;- largeStruct) {
  18. defer wg.Done()
  19. for i := 0; i &lt; 5; i++ {
  20. fmt.Printf(&quot;producer: %d\n&quot;, i)
  21. output &lt;- largeStruct{}
  22. }
  23. close(output)
  24. }
  25. func consumer(wg *sync.WaitGroup, input &lt;-chan largeStruct) {
  26. defer wg.Done()
  27. i := 0
  28. LOOP:
  29. for {
  30. select {
  31. case _, ok := &lt;-input:
  32. if !ok {
  33. break LOOP
  34. }
  35. fmt.Printf(&quot;consumer: %d\n&quot;, i)
  36. i++
  37. }
  38. }
  39. }

Playground: http://play.golang.org/p/fawEQnSDwB

答案1

得分: 24

是的,在Go语言中,一切都是复制的,你可以通过将通道改为使用指针(即chan *largeStruct)来轻松解决这个问题。

// 示例:http://play.golang.org/p/CANxwt8s2B

正如你所看到的,在每种情况下,v.buf的指针是不同的,但是如果你将其改为chan *largeStruct,指针将会相同。

@LucasJones提供了一个更容易理解的示例:https://play.golang.org/p/-VFWCgOnh0

正如@nos指出的,如果在发送后的两个goroutine中都修改了值,可能会出现潜在的竞争问题。

英文:

Yes, everything is a copy in Go, you can easily work around that by changing the channel to use a pointer (aka chan *largeStruct).

// demo: http://play.golang.org/p/CANxwt8s2B

As you can see, the pointer to v.buf is different in each case, however if you change it to chan *largeStruct, the pointers will be the same.

@LucasJones provided a little easier to follow example: https://play.golang.org/p/-VFWCgOnh0

As @nos pointed out, there's a potential race if you modify the value in both goroutines after sending it.

答案2

得分: 8

《Go编程语言规范》

发送语句

发送语句在通道上发送一个值。通道表达式必须是通道类型,通道的方向必须允许发送操作,要发送的值的类型必须可以赋值给通道的元素类型。

这是一个副本,因为通过将值分配给通道的元素类型来将值发送到通道。如果值是一个结构体,则会复制该结构体。如果值是指向结构体的指针,则会复制指向结构体的指针。

英文:

> The Go Programming Language Specification
>
> Send statements
>
> A send statement sends a value on a channel. The channel expression
> must be of channel type, the channel direction must permit send
> operations, and the type of the value to be sent must be assignable to
> the channel's element type.

It's a copy because the value is sent to the channel by assignment to the channel's element type. If the value is a struct, then the struct is copied. If the value is a pointer to a struct, then the pointer to the struct is copied.

huangapple
  • 本文由 发表于 2016年3月7日 21:55:52
  • 转载请务必保留本文链接:https://go.coder-hub.com/35845596.html
匿名

发表评论

匿名网友

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

确定