go 通过channel异步读写切片,切片内容不符预期 - Go语言中文社区

go 通过channel异步读写切片,切片内容不符预期


首先来看下下面的一段代码,会error输出吗?

package main

import (
	"os"
	"bufio"
	"io"
	"fmt"
)

func main() {
	err := ddImage(`E:大文件.txt`, ``)
	if err != nil {
		fmt.Println(err)
		return
	}
	fmt.Println("程序执行完毕")
}

type Resource struct {
	Index           uint64
	Buffer          []byte
	Size            int
	Err             error
	RefreshedBuffer []byte
}

func ddImage(ddPath, ddDstPath string) error {
	reader, err := os.Open(ddPath)
	if err != nil {
		return err
	}
	defer reader.Close()
	br := bufio.NewReader(reader)

	//writer, err := os.OpenFile(ddDstPath, os.O_CREATE|os.O_WRONLY|os.O_TRUNC, os.ModePerm)
	//if err != nil {
	//	return err
	//}
	//defer writer.Close()

	const bufferSize int64 = 1024 * 1024
	chanCount := 30
	writeChan := make(chan *Resource, chanCount)

	go readBufioData(br, bufferSize, writeChan)

	for {
		data := <-writeChan

		if data.Err != nil && data.Err != io.EOF {
			return data.Err
		}
		if data.Size > 0 {
			if !CompareSlice(data.Buffer, data.RefreshedBuffer) {
				fmt.Printf("Buffer:%p, RefreshedBuffer:%pn", data.Buffer, data.RefreshedBuffer)
				fmt.Printf("&Buffer:%p, &RefreshedBuffer:%pn", &data.Buffer, &data.RefreshedBuffer)
				return fmt.Errorf("两个slice不同了")
			}
			//if _, err = writer.Write(data.Buffer); err != nil {
			//	return err
			//}
		}

		if data.Err == io.EOF {
			return nil
		}
		if data.Err != nil {
			return data.Err
		}
	}
}

func readBufioData(reader *bufio.Reader, bufferSize int64, compChan chan *Resource) {
	buf := make([]byte, bufferSize)
	for {
		refreshedBuf := buf
		n, err := reader.Read(buf)

		resource := new(Resource)
		resource.Size = n
		resource.Buffer = buf[:n]
		resource.Err = err
		resource.RefreshedBuffer = refreshedBuf[:n]

		compChan <- resource

		if err != nil {
			if err != io.EOF {
				fmt.Println(err)
			}
			break
		}
	}
	fmt.Println("read exit")
}

func CompareSlice(a, b []byte) bool {
	if len(a) != len(b) {
		return false
	}

	if (a == nil) != (b == nil) {
		return false
	}

	for key, value := range a {
		if value != b[key] {
			return false
		}
	}

	return true
}

可能的结果

  1. 没有error输出
read exit
程序执行完毕
  1. error了
Buffer:0xc000092000, RefreshedBuffer:0xc000092000
&Buffer:0xc0001940f8, &RefreshedBuffer:0xc000194128
两个slice不同了

为什么会出现不同的输出结果呢?

先来看下传的值

  1. 当文件比较小,有可能错误,也有可能正确输出
  2. 文件较大,正常情况下都会错误输出

接下来,主要看结构体ResourceBufferRefreshedBuffer在传输中的区别
在这里插入图片描述
Buffer是使用的一开始只初始化一次的buf来作为源数据,而RefreshedBuffer是相当于每次重新分配了新的地址空间的buf。

我们再添加一些日志信息打印,看下bufresourceresource.Buffer 的地址信息。

func readBufioData(reader *bufio.Reader, bufferSize int64, compChan chan *Resource) {
	buf := make([]byte, bufferSize)
	for {
		refreshedBuf := buf
		n, err := reader.Read(buf)

		resource := new(Resource)
		resource.Size = n
		resource.Buffer = buf[:n]
		resource.Err = err
		resource.RefreshedBuffer = refreshedBuf[:n]
		fmt.Printf("buf:%p, resource:%p, resource.Buffer:%pn", buf, resource, resource.Buffer)

		compChan <- resource

		if err != nil {
			if err != io.EOF {
				fmt.Println(err)
			}
			break
		}
	}
	fmt.Println("read exit")
}

可以得到如下输出
在这里插入图片描述
由此可见,当 resource 通过channel传输的时候,虽然每个resource都是新分配地址的值,但是 resource.Buffer 却指向的仍然是原始的buf的地址。

所以,在channel的接收端,会出现 BufferRefreshedBuffer 不相等的情况,即:由于channel是带缓存的,channel的缓存还未及时读完,之前的 Buffer 切片内容已经被修改,导致channel缓存中的 Buffer 都变成了最新的输入端的 buf 值。

总结

对于经常接触 C/C++ 的人来说,这种问题应该不太会出现。在go中使用切片传值的时候,一定要注意切片的引用传值的问题。类似这种for循环写切片的时候,若对性能没有很强烈的要求,可以每次都 make 一个新的切片。go中切片的坑还是挺多的,使用时一定要多留意一下。

版权声明:本文来源CSDN,感谢博主原创文章,遵循 CC 4.0 by-sa 版权协议,转载请附上原文出处链接和本声明。
原文链接:https://blog.csdn.net/DisMisPres/article/details/114554524
站方申明:本站部分内容来自社区用户分享,若涉及侵权,请联系站方删除。

0 条评论

请先 登录 后评论

官方社群

GO教程

猜你喜欢