Недавно я понял, что не знаю, как правильно 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()
, потому что мьютекс в конечном итоге будет заблокирован в основном все время. Чтобы заставить его работать правильно, нам потребуется какое-то взаимодействие между чтением и блокировкой, как это делают условные переменные: мьютекс разблокируется до того, как горутина будет ждать, и снова заблокируется, когда горутина проснется.
Я бы реализовал что-то подобное вручную, но тогда мне нужен какой-то способ select
во время чтения. Вот так: (псевдокод)
select {
case b := <-f.NextByte():
// process the byte somehow
default:
}
Я изучил документы пакетов os и sync, и пока я не вижу способа сделать это.
/dev/ttyUSB0
, и не закрою файл, то файл/dev/ttyUSB0
все равно будет открыт, а когда я снова подключу устройство, он станет/dev/ttyUSB1
. Мне нужно, чтобы снова было/dev/ttyUSB0
. - person Dmitry Frank   schedule 18.01.2017Read
действительно вернетio.EOF
, но вопрос более общий. Например. рассмотрите возможность наличия горутины, которая считывает данные из последовательного порта и отправляет полученные данные на какой-либо канал, а у пользователя есть кнопка отключения, которая, очевидно, должна закрывать файл, и делать это без гонок. - person Dmitry Frank   schedule 18.01.2017