使用golang通道缺少数据

Lack of data using golang channel

我遇到了一个奇怪的问题。脚本如下。

package main

import (
    "fmt"
    "sync"
)

type Data struct {
    data []int
}

func main() {
    ws := 5
    ch := make(chan *Data, ws)
    var wg sync.WaitGroup
    for i := 0; i < ws; i++ {
        wg.Add(1)
        go func(wg *sync.WaitGroup, ch chan *Data) {
            defer wg.Done()
            for {
                char, ok := <-ch
                if !ok {
                    return
                }
                fmt.Printf("Get: %d\n", len(char.data))
            }
        }(&wg, ch)
    }
    var d Data
    ar := []int{1}
    for i := 0; i < ws; i++ {
        d.data = []int{}
        for j := 0; j < 1000; j++ {
            d.data = append(d.data, ar[0])
        }
        ch <- &d
        // time.Sleep(time.Second / 1000) // When this line is moved, a number of data by put and get becomes same.
        fmt.Printf("Put: %d\n", len(d.data))
    }
    close(ch)
    wg.Wait()
}

这是运行,预计会出现以下结果。 "Put"和"Get"的数据个数相同。

Put: 1000
Get: 1000
Put: 1000
Get: 1000
Put: 1000
Get: 1000
Put: 1000
Get: 1000
Put: 1000
Get: 1000

但是,不可能每次都得到这个结果。结果如下。 "Put"和"Get"的数据个数每次都不一样

尝试 1

Put: 1000
Get: 1000
Put: 1000
Get: 1000
Put: 1000
Get: 1000
Put: 1000
Get: 1000
Put: 1000
Get: 1000

尝试 2

Put: 1000
Get: 1000
Put: 1000
Get: 1000
Put: 1000
Get: 1000
Put: 1000
Get: 16
Put: 1000
Get: 0

尝试 3

Get: 1000
Put: 1000
Put: 1000
Get: 1
Put: 1000
Get: 1000
Put: 1000
Get: 1
Put: 1000
Get: 1000

虽然在我的电脑上,"Put"和"Get"的数据数量每次都不同,但在play.golang.org,两个数据的数量总是相同的。 https://play.golang.org/p/QFSuZmZk7d 为什么?

如果在脚本中使用time.Sleep(time.Second / 1000),则两个数据的数量相同。如果你知道这个问题,请你教我。非常感谢您的宝贵时间。

您观察到的是"data race"的例子。

当您同时访问同一块数据(其中至少一个是写入)时会发生这种情况。

你每次都引用同一个结构。接下来可能发生的是少数几种可能性之一:

  1. 它在你改变它之前在频道的另一边被读取("expected" 场景)

  2. 你在它被阅读之前就开始变异了。在这种情况下,接收方可以读取任意数量的 Data.data 个项目,从 0 到 1000,具体取决于读取发生的确切时间。

问题有多种解决方案:

  1. 您可以在每次迭代中创建 Data 的新实例。为此,只需将 var d Data 声明移动到循环体内即可。在这种情况下,每次迭代都会创建一个新结构,因此您可能不会错误地改变前一个结构。

  2. 您可以声明 Data 的通道(结构,而不是指向结构的指针):chan Data。在这种情况下,每次将 Data 实例发送到通道时都会隐式复制它(因为 Go 中的所有内容都是按值传递的,在赋值时复制)。

package main

import (
    "fmt"
    "sync"
)

/*
    信号量的考察,put 之后,必须等待 get 拿到之后才能推出循环
*/

type Data struct {
    data []int
}

func main() {
    ws := 5
    ch := make(chan *Data, ws)

    sem := make(chan bool)

    var wg sync.WaitGroup

    for i := 0; i < ws; i++ {
        wg.Add(1)

        go func(wg *sync.WaitGroup, ch chan *Data) {
            defer wg.Done()

            for {
                char, ok := <-ch
                if !ok {
                    return
                }
                fmt.Printf("Get: %d\n", len(char.data))
                sem <- true
            }
        }(&wg, ch)
    }

    var d Data
    ar := []int{1}

    // ws = 5
    for i := 0; i < ws; i++ {
        d.data = []int{}

        for j := 0; j < 1000; j++ {
            d.data = append(d.data, ar[0])
        }

        ch <- &d

        fmt.Printf("Put: %d\n", len(d.data))

        <-sem // 一个信号量,必须等待 get 完成之后才能继续put
    }
    close(ch)

    wg.Wait()
}