我最近意识到我并不知道如何在Go中同时正确Read
和Close
。在我的特定情况下,我需要使用串行端口,但问题更通用。
如果我们在没有任何额外努力同步事情的情况下这样做,就会导致竞争条件。简单的例子:
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:
}
答案 0 :(得分:-1)
我相信你需要2个信号:
当然你可以选择你喜欢的去信令原语(频道,等候组,上下文等)。
下面的示例,我使用waitgroup和context。原因是 你可以旋转多个阅读器,只需关闭上下文就可以告诉所有读者去常规停止。
我创建了多个go例程 一个例子,你甚至可以用它来协调多个例程。
package main
import (
"context"
"fmt"
"os"
"sync"
"time"
)
func main() {
ctx, cancelFn := context.WithCancel(context.Background())
f, err := os.Open("/dev/ttyUSB0")
if err != nil {
panic(err)
}
var wg sync.WaitGroup
for i := 0; i < 3; i++ {
wg.Add(1)
// Start a goroutine which keeps reading from a serial port
go func(i int) {
defer wg.Done()
reader(ctx, f)
fmt.Printf("reader %d closed\n", i)
}(i)
}
time.Sleep(1000 * time.Millisecond)
fmt.Println("closing")
cancelFn() // signal all reader to stop
wg.Wait() // wait until all reader finished
f.Close()
fmt.Println("file closed")
time.Sleep(1000 * time.Millisecond)
}
func reader(ctx context.Context, f *os.File) {
b := make([]byte, 100)
for {
select {
case <-ctx.Done():
return
default:
f.Read(b)
}
}
}