首先来看下下面的一段代码,会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:%p\n", data.Buffer, data.RefreshedBuffer)
fmt.Printf("&Buffer:%p, &RefreshedBuffer:%p\n", &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
}
可能的结果
- 没有error输出
read exit
程序执行完毕
- error了
Buffer:0xc000092000, RefreshedBuffer:0xc000092000
&Buffer:0xc0001940f8, &RefreshedBuffer:0xc000194128
两个slice不同了
为什么会出现不同的输出结果呢?
先来看下传的值
- 当文件比较小,有可能错误,也有可能正确输出
- 文件较大,正常情况下都会错误输出
接下来,主要看结构体Resource的Buffer和RefreshedBuffer在传输中的区别

Buffer是使用的一开始只初始化一次的buf来作为源数据,而RefreshedBuffer是相当于每次重新分配了新的地址空间的buf。
我们再添加一些日志信息打印,看下buf,resource,resource.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:%p\n", 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的接收端,会出现 Buffer 和 RefreshedBuffer 不相等的情况,即:由于channel是带缓存的,channel的缓存还未及时读完,之前的 Buffer 切片内容已经被修改,导致channel缓存中的 Buffer 都变成了最新的输入端的 buf 值。
总结
对于经常接触 C/C++ 的人来说,这种问题应该不太会出现。在go中使用切片传值的时候,一定要注意切片的引用传值的问题。类似这种for循环写切片的时候,若对性能没有很强烈的要求,可以每次都 make 一个新的切片。go中切片的坑还是挺多的,使用时一定要多留意一下。
578

被折叠的 条评论
为什么被折叠?



