2017-01-18 3 views
1

Ich habe vor kurzem festgestellt, dass ich nicht weiß, wie man Read und Close in Go gleichzeitig richtig. In meinem speziellen Fall muss ich das mit einer seriellen Schnittstelle machen, aber das Problem ist allgemeiner.Gleichzeitig lesen/schließen in Go, in einer plattformübergreifenden Weise

Wenn wir das ohne zusätzliche Anstrengung tun, um Dinge zu synchronisieren, führt das zu einer Wettlaufsituation. Einfaches Beispiel:

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

Wenn wir die oben als main.go speichern und starten Sie go run --race main.go, sieht die Ausgabe wie folgt:

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 

Ok, aber wie das richtig zu handhaben? Natürlich können wir nicht einfach einen Mutex vor dem Aufruf von f.Read() sperren, da der Mutex im Grunde die ganze Zeit gesperrt bleibt. Damit es richtig funktioniert, benötigen wir eine Art von Zusammenarbeit zwischen Lesen und Sperren, wie es bei Bedingungsvariablen der Fall ist: Der Mutex wird entsperrt, bevor er auf die Goroutine wartet, und er wird gesperrt, wenn die Goroutine aufwacht.

Ich würde so etwas manuell implementieren, aber dann brauche ich einen Weg zu select Dinge beim Lesen. Wie folgt aus: (Pseudo-Code)

select { 
case b := <-f.NextByte(): 
    // process the byte somehow 
default: 
} 

Ich untersuchte Dokumente der Pakete os und sync, und so weit ich sehe keine Möglichkeit, das zu tun.

+0

Müssen Sie die Datei wirklich schließen? Die sicherste Methode ist, die Lese-Routine bis zum Beenden des Prozesses zu verlassen. – JimB

+0

Ich sehe nicht, warum Sie ein Datei-Handle über verschiedene Thread der Ausführung oder für jede Ressource verwenden möchten. Es macht nur Ihren Code komplex. – Ankur

+0

@ JimB, muss ich die Datei schließen, um die Wiederverbindung zu implementieren: z. Wenn ich das Gerät lösche, dessen Knoten '/ dev/ttyUSB0' ist, und ich die Datei nicht schließe, dann wird die Datei'/dev/ttyUSB0' weiterhin geöffnet, und wenn ich das Gerät wieder anschließe, wird es '/ dev/ttyUSB1'. Ich brauche es wieder '/ dev/ttyUSB0'. –

Antwort

-1

Ich glaube Sie brauchen zwei Signale:

  1. main -> Leser, es zu sagen,
  2. Leser aufhören zu lesen -> Haupt, dass Leser zu sagen hat

von beendet Natürlich können Sie Go-Signalisierung Primitive (Kanal, Wartegruppe, Kontext usw.) wählen, die Sie bevorzugen.

Beispiel unten verwende ich waitgroup und Kontext. Der Grund ist , dass Sie mehrere Leser drehen können und nur den Kontext schließen müssen, um allen Leser zu sagen, dass die Routine zu stoppen ist.

Ich erstellte mehrere gehen Routine wie ein Beispiel, das Sie sogar mehrere Routine mit ihm koordinieren können.

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

A Lesen ohne eingehende Daten wird unbegrenzt blockiert. Dies kann einen Leseanruf nicht unterbrechen. – JimB

+0

Sie könnten Recht haben, ich werde sehen, ob ich es zu ändern ch <-f.Read (b) anstelle von Standard: – ahmy

+0

Das ändert immer noch nichts, weil das Lesen zuerst ausgewertet wird. Sie können ein Read nicht direkt in einer Datei auf Go (für jetzt) ​​unterbrechen. – JimB

Verwandte Themen