我最近意识到我不知道如何在Go中正确地使用Read
和Close
。在我的特殊情况下,我需要用串口来做这件事,但问题更为普遍。并行读取/关闭在Go中,以跨平台的方式
如果我们这样做没有任何额外的努力来同步的事情,它会导致竞争条件。简单的例子:
package main
import (
"fmt"
"os"
"time"
)
func main() {
f, err := os.Open("/dev/ttyUSB0")
if err != nil {
panic(err)
}
// Start a goroutine which keeps reading from a serial port
go reader(f)
time.Sleep(1000 * time.Millisecond)
fmt.Println("closing")
f.Close()
time.Sleep(1000 * time.Millisecond)
}
func reader(f *os.File) {
b := make([]byte, 100)
for {
f.Read(b)
}
}
如果我们上面保存为main.go
,并运行go run --race main.go
,输出将如下所示:
closing
==================
WARNING: DATA RACE
Write at 0x00c4200143c0 by main goroutine:
os.(*file).close()
/usr/local/go/src/os/file_unix.go:143 +0x124
os.(*File).Close()
/usr/local/go/src/os/file_unix.go:132 +0x55
main.main()
/home/dimon/mydata/projects/go/src/dmitryfrank.com/testfiles/main.go:20 +0x13f
Previous read at 0x00c4200143c0 by goroutine 6:
os.(*File).read()
/usr/local/go/src/os/file_unix.go:228 +0x50
os.(*File).Read()
/usr/local/go/src/os/file.go:101 +0x6f
main.reader()
/home/dimon/mydata/projects/go/src/dmitryfrank.com/testfiles/main.go:27 +0x8b
Goroutine 6 (running) created at:
main.main()
/home/dimon/mydata/projects/go/src/dmitryfrank.com/testfiles/main.go:16 +0x81
==================
Found 1 data race(s)
exit status 66
好的,但如何处理是否正确?当然,在调用f.Read()
之前,我们不能只锁定一些互斥锁,因为互斥锁基本上始终都会锁定。为了使它正常工作,我们需要在读取和锁定之间进行某种协作,就像条件变量一样:在goroutine等待之前,互斥锁被解锁,并且在goroutine唤醒时它被锁定。
我会实现类似这样的手动,但然后我需要一些方法来select
事情,而阅读。像这样的:(伪)
select {
case b := <-f.NextByte():
// process the byte somehow
default:
}
我检查包os和sync的文档,到目前为止我没有看到任何方式做到这一点。
您确实需要关闭文件吗?最安全的方法是离开阅读门厅,直到进程退出。 – JimB
我不明白你为什么要在不同的执行线程中使用文件句柄,或者为什么要使用任何资源。它只会让你的代码变得复杂。 – Ankur
@JimB,我确实需要关闭这个文件才能实现重新连接。当我拔掉节点为“/ dev/ttyUSB0”的设备时,如果我没有关闭文件,那么文件'/ dev/ttyUSB0'仍然会打开,当我插回设备时,它会变成'的/ dev/ttyUSB1'。我需要它再次成为'/ dev/ttyUSB0'。 –