为何从通道取数据顺序是错误的?

2022-08-08 17:29:31 +08:00
 yunshangdetianya

新手学习 go ,从通道中获取数据为什么顺序是错乱的,下面是我写的伪代码

package main

import (
	"fmt"
	"io"
	"net/http"
	"os"
	"sync"
)

var wg sync.WaitGroup

func Gets(uri string, buf chan []byte) {
	resp, err := http.Get(uri)
	if err != nil {
		fmt.Println(err)
		return
	}
	defer resp.Body.Close()
	bufs := make([]byte, 128)
	for {
		n, err1 := resp.Body.Read(bufs[:])
		if err1 == io.EOF {
			break
		}
		buf <- bufs[:n]
		fmt.Println(string(bufs[:n]))
	}
	fmt.Println("读取数据完毕")
	close(buf)
}

func SaveFile(name string, buf chan []byte) {
	defer wg.Done()
	f, err2 := os.Create(name)
	if err2 != nil {
		return
	}
	defer f.Close()
	for {
		if data, ok := <-buf; ok {
			f.Write(data)

		} else {
			break
		}
	}
	f.Sync()
	fmt.Println("写入数据完毕")
}

func main() {
	for i := 1; i < 2; i++ {
		wg.Add(1)
		buf := make(chan []byte)
		files1 := "test1.txt"
		urils := "http://www.baidu.cn"
		go SaveFile(files1, buf)
		go Gets(urils, buf)
	}
	wg.Wait()
}

fmt.Println(string(bufs[:n]))这个和 test1.txt 中内容不一致啊,这个是为什么,各位大神帮忙分析下。

1323 次点击
所在节点    Go 编程语言
14 条回复
yunshangdetianya
2022-08-08 17:38:39 +08:00
如果把通道改为带缓冲的,那么顺序就更乱了
```
buf := make(chan []byte,128)
```
useben
2022-08-08 18:11:20 +08:00
for i := 1; i < 2; i++ {
wg.Add(1)
buf := make(chan []byte)
files1 := "test1.txt"
urils := "http://www.baidu.cn"
go SaveFile(files1, buf)
go Gets(urils, buf)
}

分别创建 2 个 SaveFile/Gets 的 goroutine 了....
hsfzxjy
2022-08-08 18:16:43 +08:00
因为你多次<-使用的是同一个 buffer ,可能 SaveFile 里 f.Write 还没执行完,Gets 里 resp.Body.Read 就把上一次的内容覆盖了
keepeye
2022-08-08 18:22:25 +08:00
你分别创建了两个读 两个写的线程,并行的,肯定乱了
keepeye
2022-08-08 18:22:56 +08:00
不要把 go 当作 await 用
yunshangdetianya
2022-08-09 09:34:54 +08:00
@useben 让我好好想想,感觉没转过来
yunshangdetianya
2022-08-09 09:35:25 +08:00
@keepeye 我好像还没反应过来
yunshangdetianya
2022-08-09 09:37:08 +08:00
@keepeye 那个 for 循环不是只执行一次吗? for i := 1; i < 2; i++
yunshangdetianya
2022-08-09 09:37:32 +08:00
@keepeye 为何会创建 2 个呢?
keepeye
2022-08-09 09:45:16 +08:00
抱歉,看错了,重新过了一遍代码,问题可能出在 buf <- bufs[:n] 这句上,切片是引用,你最好 copy 到一个新的 slice 上:

newBuf := make([]byte, n)
copy(newBuf, bufs[:n])
buf <- newBuf

试一下看看
yunshangdetianya
2022-08-09 09:56:24 +08:00
@keepeye 好的,我去试下,非常感谢
yunshangdetianya
2022-08-09 09:59:53 +08:00
@keepeye 的确是可以了,正常了,但是为何拷贝过去就正常了呢?
keepeye
2022-08-09 10:15:32 +08:00
因为切片的切片还是指向同一个底层数组,在 write 的时候底层数组也同时被 read 操作覆盖了。

举个例子:
a := []int{1,2,3}
b := a[:1]
a[0] = 4
fmt.Println(b) // [4]

具体可以阅读这篇文章:

https://go.dev/blog/slices-intro
yunshangdetianya
2022-08-09 10:23:49 +08:00
@keepeye 非常感谢,我去看下文章

这是一个专为移动设备优化的页面(即为了让你能够在 Google 搜索结果里秒开这个页面),如果你希望参与 V2EX 社区的讨论,你可以继续到 V2EX 上打开本讨论主题的完整版本。

https://www.v2ex.com/t/871477

V2EX 是创意工作者们的社区,是一个分享自己正在做的有趣事物、交流想法,可以遇见新朋友甚至新机会的地方。

V2EX is a community of developers, designers and creative people.

© 2021 V2EX