2017-01-18 8 views
1

Я недавно понял, что не знаю, как правильно Read и Close в Go одновременно. В моем конкретном случае мне нужно сделать это с помощью последовательного порта, но проблема более общая.Параллельное чтение/закрытие в 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, и до сих пор я не вижу какой-либо способ сделать это.

+0

Действительно ли вам нужно закрыть файл? Самый безопасный метод - просто оставить чтение goroutine до завершения процесса. – JimB

+0

Я не понимаю, почему вы хотели бы использовать дескриптор файла в разных потоках исполнения или, в этом отношении, на любом ресурсе. Это сделает ваш код сложным. – Ankur

+0

@JimB, мне нужно закрыть файл, чтобы реализовать пересоединение: например. когда я отсоединяю устройство, чей узел был '/ dev/ttyUSB0', и я не закрываю файл, тогда файл'/dev/ttyUSB0' все равно будет открыт, и когда я подключу устройство обратно, он станет '/ DEV/ttyUSB1'. Мне нужно, чтобы он снова был '/ dev/ttyUSB0'. –

ответ

-1

Я верю, что вам нужно 2 сигнала:

  1. главная -> читатель, чтобы сказать ему, чтобы прекратить читать
  2. читателя -> главная, чтобы сказать, что читателю было прекращено

из Конечно, вы можете выбрать примитивный сигнальный примитив (канал, waitgroup, context и т. д.), который вы предпочитаете.

Пример ниже, я использую waitgroup и контекст. Причиной является , что вы можете вращать несколько считывателей, и нужно только закрыть контекст, чтобы рассказать все, что нужно сделать для чтения.

Я создал процедуру множественного доступа так же, как пример, который вы можете даже координировать с ним несколько рутинных процедур.

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) 
     } 
    } 
} 
+1

Чтение без входящих данных будет блокироваться бесконечно. Это не может прерывать вызов Read. – JimB

+0

Возможно, вы правы, я посмотрю, изменил ли я его на ch <-f.Read (b) вместо значения по умолчанию: – ahmy

+0

Это все равно ничего не меняет, потому что сначала оценивается Read. Вы просто не можете прервать чтение непосредственно в файле на Go (пока). – JimB