缺乏使用Golang通道的数据

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

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) // 当移动这行代码时,放入和取出的数据数量就会相同。
		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上,两者的数量总是相同的。为什么会这样?如果在脚本中使用time.Sleep(time.Second / 1000),那么两者的数据数量就会相同。如果你了解这个问题,请告诉我。非常感谢你的时间。

英文:

I encountered a strange problem. The script is below.

package main

import (
	&quot;fmt&quot;
	&quot;sync&quot;
)

type Data struct {
	data []int
}

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

This is run, a following result is expected. The number of data for "Put" and "Get" is same.

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

But, this result cannot be got every time. The results are below. The number of data of "Put" and "Get" is different for every time.

Try 1

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

Try 2

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

Try 3

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

ALthough on my PC, the number of data of "Put" and "Get" is different for every time, at play.golang.org, the number of both data is always same. https://play.golang.org/p/QFSuZmZk7d Why?

If time.Sleep(time.Second / 1000) is used in the script, the number of both data becomes same. If you know about this problem, will you please teach me. Thank you so much for your time.

答案1

得分: 0

你观察到的是一个"数据竞争"的例子。

当你同时访问同一块数据时(至少其中一个是写操作),就会发生数据竞争。

你每次都将引用放在相同的结构体上。接下来可能发生的情况有几种可能性:

  1. 在你修改数据之前,它已经被另一端的通道读取("预期"的情况)。

  2. 在它被读取之前,你开始对其进行修改。在这种情况下,接收方可能会读取任意数量的Data.data项,从0到1000,具体取决于读取发生的时间。

解决这个问题有多种方法:

  1. 每次迭代都可以创建Data的新实例。为此,只需将var d Data的声明放在循环体内部。这样,每次迭代都会创建一个新的结构体,因此你不会错误地修改先前的结构体。

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

英文:

What you observe is an example of "data race".

It happens when you concurrently access the same piece of data (with at least one of those being a write).

You put a reference to the same structure every time. And what may happen next is one of few possibilities:

  1. it was read on the other side of the channel before you mutated it (the "expected" scenario)

  2. you started mutating it before it was read. In this case the receiver may read any number of Data.data items, from 0 to 1000, depending on when exactly the read happened.

There are multiple solutions for the problem:

  1. You may create the new instance of Data every iteration. For that simply move the var d Data declaration inside the loop body. In this case every iteration a new structure is created, so you may not mutate the previous one by mistake.

  2. You may declare channel of Data (the structures, not pointers to a structure): chan Data. In this case the Data instance is implicitly copied every time you send it to the channel (since everything in Go is passed by value, copied on assignment).

答案2

得分: 0

package main

import (
	"fmt"
	"sync"
)

/*
	Examining semaphores, after putting, you must wait for get to retrieve before exiting the loop
*/

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 // A semaphore, must wait for get to complete before continuing to put
	}
	close(ch)

	wg.Wait()
}
英文:
package main
import (
&quot;fmt&quot;
&quot;sync&quot;
)
/*
信号量的考察,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 &lt; ws; i++ {
wg.Add(1)
go func(wg *sync.WaitGroup, ch chan *Data) {
defer wg.Done()
for {
char, ok := &lt;-ch
if !ok {
return
}
fmt.Printf(&quot;Get: %d\n&quot;, len(char.data))
sem &lt;- true
}
}(&amp;wg, ch)
}
var d Data
ar := []int{1}
// ws = 5
for i := 0; i &lt; ws; i++ {
d.data = []int{}
for j := 0; j &lt; 1000; j++ {
d.data = append(d.data, ar[0])
}
ch &lt;- &amp;d
fmt.Printf(&quot;Put: %d\n&quot;, len(d.data))
&lt;-sem // 一个信号量,必须等待 get 完成之后才能继续put
}
close(ch)
wg.Wait()
}

huangapple
  • 本文由 发表于 2017年3月28日 15:40:32
  • 转载请务必保留本文链接:https://go.coder-hub.com/43063444.html
匿名

发表评论

匿名网友

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

确定