使用切片类型的输入和输出通道来实现Go并发工作程序。

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

Go concurrent worker routines using slice type input and output channels

问题

我对Go语言还比较新手。尽管我不希望如此,但我可能会问一个愚蠢的问题。提前道歉,以防万一...

这是我的例子:我定义了一个worker()函数,它作为一组并发的Go协程从main()函数中调用。输入和输出数据通过输入和输出通道提供,它们都是切片类型[]int。在某种情况下,一切都按预期工作,而在另一种情况下,结果是错误的。请参阅代码中的注释和程序输出。

老实说,我看不出这两种代码变体之间的实际区别。我在这里错过了什么?谢谢您的任何建议!

package main

import "fmt"
import "runtime"

func worker(x_ch <-chan []int, y_ch chan<- []int, wid int) {
    
    for x := range x_ch {
        y := x
        fmt.Println("   worker", wid, "x:", x)
        fmt.Println("   worker", wid, "y:", y)
        y_ch <- y
    }
}

func main() {
    
    n_workers := runtime.NumCPU()
    n_len := 4
    n_jobs := 4
    x := make([]int, n_len)
    x_ch := make(chan []int, 10)
    y_ch := make(chan []int, 10)
    
    for j := 0; j < n_workers; j++ { go worker(x_ch, y_ch, j) }

    for k := 0; k < n_jobs; k++ {

//      variant 1: works!
        x = []int{k, k, k, k}

//      variant 2: doesn't work!
//      for i := range x { x[i] = k }

        fmt.Println("main x:", k, x)
        x_ch <- x
    }
    
    close(x_ch)
    
    for i := 0; i < n_jobs; i++ {
        z := <- y_ch
        fmt.Println("       main y:", i, z)
    }
}

正确的输出(变体1):

main x: 0 [0 0 0 0]
main x: 1 [1 1 1 1]
main x: 2 [2 2 2 2]
main x: 3 [3 3 3 3]
   worker 3 x: [3 3 3 3]
   worker 3 y: [3 3 3 3]
   worker 2 x: [2 2 2 2]
   worker 2 y: [2 2 2 2]
   worker 1 x: [0 0 0 0]
   worker 1 y: [0 0 0 0]
   worker 0 x: [1 1 1 1]
   worker 0 y: [1 1 1 1]
       main y: 0 [3 3 3 3]
       main y: 1 [2 2 2 2]
       main y: 2 [0 0 0 0]
       main y: 3 [1 1 1 1]

错误的输出(变体2):

main x: 0 [0 0 0 0]
main x: 1 [1 1 1 1]
main x: 2 [2 2 2 2]
main x: 3 [3 3 3 3]
   worker 3 x: [3 3 3 3]
   worker 3 y: [3 3 3 3]
       main y: 0 [3 3 3 3]
   worker 0 x: [2 2 2 2]
   worker 0 y: [3 3 3 3]
       main y: 1 [3 3 3 3]
   worker 1 x: [1 1 1 1]
   worker 1 y: [3 3 3 3]
       main y: 2 [3 3 3 3]
   worker 2 x: [3 3 3 3]
   worker 2 y: [3 3 3 3]
       main y: 3 [3 3 3 3]
英文:

I am relatively new to the Go language. Even though I don't hope so, I maybe bother you with a silly question. My apologies upfront, just in case...

Here's my example: I defined a worker() function which is called from main() as a set of concurrent Go routines. Input and output data is provided via an input and an output channel both of slice type []int. In one case everything works as expected, in the other case the result is faulty. See the comments in the code and the program output below the code.

Honestly, I don't see the actual difference between both code variants. What did I miss here? Thank you for any advice!

package main

import &quot;fmt&quot;
import &quot;runtime&quot;
       
func worker(x_ch &lt;-chan []int, y_ch chan&lt;- []int, wid int) {
    
    for x := range x_ch {
        y := x
        fmt.Println(&quot;   worker&quot;, wid, &quot;x:&quot;, x)
        fmt.Println(&quot;   worker&quot;, wid, &quot;y:&quot;, y)
        y_ch &lt;- y
    }
}

func main() {
    
    n_workers := runtime.NumCPU()
    n_len := 4
    n_jobs := 4
    x := make([]int, n_len)
    x_ch := make(chan []int, 10)
    y_ch := make(chan []int, 10)
    
    for j := 0; j &lt; n_workers; j++ { go worker(x_ch, y_ch, j) }

    for k := 0; k &lt; n_jobs; k++ {

//      variant 1: works!
        x = []int{k, k, k, k}

//      variant 2: doesn&#39;t work!
//      for i := range x { x[i] = k }

        fmt.Println(&quot;main x:&quot;, k, x)
        x_ch &lt;- x
    }
    
    close(x_ch)
    
    for i := 0; i &lt; n_jobs; i++ {
        z := &lt;- y_ch
        fmt.Println(&quot;       main y:&quot;, i, z)
    }
}

Correct output (variant 1):

main x: 0 [0 0 0 0]
main x: 1 [1 1 1 1]
main x: 2 [2 2 2 2]
main x: 3 [3 3 3 3]
   worker 3 x: [3 3 3 3]
   worker 3 y: [3 3 3 3]
   worker 2 x: [2 2 2 2]
   worker 2 y: [2 2 2 2]
   worker 1 x: [0 0 0 0]
   worker 1 y: [0 0 0 0]
   worker 0 x: [1 1 1 1]
   worker 0 y: [1 1 1 1]
       main y: 0 [3 3 3 3]
       main y: 1 [2 2 2 2]
       main y: 2 [0 0 0 0]
       main y: 3 [1 1 1 1]

Wrong output (variant 2):

main x: 0 [0 0 0 0]
main x: 1 [1 1 1 1]
main x: 2 [2 2 2 2]
main x: 3 [3 3 3 3]
   worker 3 x: [3 3 3 3]
   worker 3 y: [3 3 3 3]
       main y: 0 [3 3 3 3]
   worker 0 x: [2 2 2 2]
   worker 0 y: [3 3 3 3]
       main y: 1 [3 3 3 3]
   worker 1 x: [1 1 1 1]
   worker 1 y: [3 3 3 3]
       main y: 2 [3 3 3 3]
   worker 2 x: [3 3 3 3]
   worker 2 y: [3 3 3 3]
       main y: 3 [3 3 3 3]

答案1

得分: 1

区别在于,在变体1中,每次发送的是不同的切片,而在变体2中,每次发送的是相同的切片(在for循环之上创建的切片)。如果不创建新的切片,而只是将相同切片的元素设置为不同的值,那么goroutine在查看切片时会看到切片中的任意值。在变体2中,main始终会看到[3 3 3 3],因为这是在循环4次后的最终值。切片对象的值包含对底层元素的引用,而不是元素本身。关于切片的用法和内部原理,这里有一个很好的解释1

英文:

The difference is that in variant 1, you're sending a different slice every time, whereas in variant 2, you're sending the same slice every time (the one created above the for loops). Without creating a new slice, you're just setting the elements of the same slice to different values, so the goroutines see whatever values happen to be in the slice when they look at it. In variant 2, main will always see [3 3 3 3] because that's the final value after you've gone through the loop 4 times. The value of a slice object contains a reference to the underlying elements, not the elements themselves. There's a good explanation of slices here.

答案2

得分: 0

非常感谢您的解释,现在我明白问题出在哪里了。我添加了一些调试代码来输出指针地址,结果如下(稍微重新格式化输出):

变体1:

main 0 x=[0 0 0 0] &amp;x=0x1830e180 &amp;x[0]=0x1830e1e0
main 1 x=[1 1 1 1] &amp;x=0x1830e180 &amp;x[0]=0x1830e230
main 2 x=[2 2 2 2] &amp;x=0x1830e180 &amp;x[0]=0x1830e270
main 3 x=[3 3 3 3] &amp;x=0x1830e180 &amp;x[0]=0x1830e2a0
    worker 3 x=[3 3 3 3] &amp;x=0x1830e1d0 &amp;x[0]=0x1830e2a0
    worker 3 y=[3 3 3 3] &amp;y=0x1830e2e0 &amp;y[0]=0x1830e2a0
        main 0 y=[3 3 3 3] &amp;y=0x1830e2d0 &amp;y[0]=0x1830e2a0
    worker 0 x=[0 0 0 0] &amp;x=0x1830e1a0 &amp;x[0]=0x1830e1e0
    worker 0 y=[0 0 0 0] &amp;y=0x1830e370 &amp;y[0]=0x1830e1e0
        main 1 y=[0 0 0 0] &amp;y=0x1830e360 &amp;y[0]=0x1830e1e0
    worker 1 x=[1 1 1 1] &amp;x=0x1830e1b0 &amp;x[0]=0x1830e230
    worker 1 y=[1 1 1 1] &amp;y=0x1830e400 &amp;y[0]=0x1830e230
        main 2 y=[1 1 1 1] &amp;y=0x1830e3f0 &amp;y[0]=0x1830e230
    worker 2 x=[2 2 2 2] &amp;x=0x1830e1c0 &amp;x[0]=0x1830e270
    worker 2 y=[2 2 2 2] &amp;y=0x1830e480 &amp;y[0]=0x1830e270
        main 3 y=[2 2 2 2] &amp;y=0x1830e470 &amp;y[0]=0x1830e270

变体2:

main 0 x=[0 0 0 0] &amp;x=0x1830e180 &amp;x[0]=0x1830e190
main 1 x=[1 1 1 1] &amp;x=0x1830e180 &amp;x[0]=0x1830e190
main 2 x=[2 2 2 2] &amp;x=0x1830e180 &amp;x[0]=0x1830e190
main 3 x=[3 3 3 3] &amp;x=0x1830e180 &amp;x[0]=0x1830e190
    worker 3 x=[3 3 3 3] &amp;x=0x1830e1d0 &amp;x[0]=0x1830e190
    worker 3 y=[3 3 3 3] &amp;y=0x1830e2a0 &amp;y[0]=0x1830e190
        main 0 y=[3 3 3 3] &amp;y=0x1830e290 &amp;y[0]=0x1830e190
    worker 0 x=[3 3 3 3] &amp;x=0x1830e1a0 &amp;x[0]=0x1830e190
    worker 0 y=[3 3 3 3] &amp;y=0x1830e330 &amp;y[0]=0x1830e190
        main 1 y=[3 3 3 3] &amp;y=0x1830e320 &amp;y[0]=0x1830e190
    worker 1 x=[3 3 3 3] &amp;x=0x1830e1b0 &amp;x[0]=0x1830e190
    worker 1 y=[3 3 3 3] &amp;y=0x1830e3c0 &amp;y[0]=0x1830e190
        main 2 y=[3 3 3 3] &amp;y=0x1830e3b0 &amp;y[0]=0x1830e190
    worker 2 x=[3 3 3 3] &amp;x=0x1830e1c0 &amp;x[0]=0x1830e190
    worker 2 y=[3 3 3 3] &amp;y=0x1830e440 &amp;y[0]=0x1830e190
        main 3 y=[3 3 3 3] &amp;y=0x1830e430 &amp;y[0]=0x1830e190
英文:

Thanks a lot for your explanation, now I see where the problem is. I added some debug code to output the pointer addresses and the result is (with slighty reformatted output):

Variant 1:

main 0 x=[0 0 0 0] &amp;x=0x1830e180 &amp;x[0]=0x1830e1e0
main 1 x=[1 1 1 1] &amp;x=0x1830e180 &amp;x[0]=0x1830e230
main 2 x=[2 2 2 2] &amp;x=0x1830e180 &amp;x[0]=0x1830e270
main 3 x=[3 3 3 3] &amp;x=0x1830e180 &amp;x[0]=0x1830e2a0
    worker 3 x=[3 3 3 3] &amp;x=0x1830e1d0 &amp;x[0]=0x1830e2a0
    worker 3 y=[3 3 3 3] &amp;y=0x1830e2e0 &amp;y[0]=0x1830e2a0
        main 0 y=[3 3 3 3] &amp;y=0x1830e2d0 &amp;y[0]=0x1830e2a0
    worker 0 x=[0 0 0 0] &amp;x=0x1830e1a0 &amp;x[0]=0x1830e1e0
    worker 0 y=[0 0 0 0] &amp;y=0x1830e370 &amp;y[0]=0x1830e1e0
        main 1 y=[0 0 0 0] &amp;y=0x1830e360 &amp;y[0]=0x1830e1e0
    worker 1 x=[1 1 1 1] &amp;x=0x1830e1b0 &amp;x[0]=0x1830e230
    worker 1 y=[1 1 1 1] &amp;y=0x1830e400 &amp;y[0]=0x1830e230
        main 2 y=[1 1 1 1] &amp;y=0x1830e3f0 &amp;y[0]=0x1830e230
    worker 2 x=[2 2 2 2] &amp;x=0x1830e1c0 &amp;x[0]=0x1830e270
    worker 2 y=[2 2 2 2] &amp;y=0x1830e480 &amp;y[0]=0x1830e270
        main 3 y=[2 2 2 2] &amp;y=0x1830e470 &amp;y[0]=0x1830e270

Variant 2:

main 0 x=[0 0 0 0] &amp;x=0x1830e180 &amp;x[0]=0x1830e190
main 1 x=[1 1 1 1] &amp;x=0x1830e180 &amp;x[0]=0x1830e190
main 2 x=[2 2 2 2] &amp;x=0x1830e180 &amp;x[0]=0x1830e190
main 3 x=[3 3 3 3] &amp;x=0x1830e180 &amp;x[0]=0x1830e190
    worker 3 x=[3 3 3 3] &amp;x=0x1830e1d0 &amp;x[0]=0x1830e190
    worker 3 y=[3 3 3 3] &amp;y=0x1830e2a0 &amp;y[0]=0x1830e190
        main 0 y=[3 3 3 3] &amp;y=0x1830e290 &amp;y[0]=0x1830e190
    worker 0 x=[3 3 3 3] &amp;x=0x1830e1a0 &amp;x[0]=0x1830e190
    worker 0 y=[3 3 3 3] &amp;y=0x1830e330 &amp;y[0]=0x1830e190
        main 1 y=[3 3 3 3] &amp;y=0x1830e320 &amp;y[0]=0x1830e190
    worker 1 x=[3 3 3 3] &amp;x=0x1830e1b0 &amp;x[0]=0x1830e190
    worker 1 y=[3 3 3 3] &amp;y=0x1830e3c0 &amp;y[0]=0x1830e190
        main 2 y=[3 3 3 3] &amp;y=0x1830e3b0 &amp;y[0]=0x1830e190
    worker 2 x=[3 3 3 3] &amp;x=0x1830e1c0 &amp;x[0]=0x1830e190
    worker 2 y=[3 3 3 3] &amp;y=0x1830e440 &amp;y[0]=0x1830e190
        main 3 y=[3 3 3 3] &amp;y=0x1830e430 &amp;y[0]=0x1830e190

huangapple
  • 本文由 发表于 2017年1月22日 04:59:17
  • 转载请务必保留本文链接:https://go.coder-hub.com/41784439.html
匿名

发表评论

匿名网友

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

确定