2017-01-18 12 views
1

私は最近、Goで同時にReadCloseを正しく理解する方法がわかりません。私の特定のケースでは、私はシリアルポートでそれを行う必要がありますが、問題はより一般的です。並行読み込み/終了、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 

[OK]を、どのように適切にそれを処理するために:私たちはmain.goとして上記を保存し、go run --race main.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) 
    } 
} 

場合は、次のように出力が見えるだろうか?もちろん、f.Read()を呼び出す前にいくつかのミューテックスをロックすることはできません。なぜなら、ミューテックスは基本的にすべての時間ロックされてしまうからです。正常に動作させるためには、条件変数のように読み込みとロックの間に何らかの協力が必要です。ゴルーチンを待たせる前にmutexがロックされ、ゴルーチンが起きたときにロックバックされます。

私は手動でこのようなものを実装しますが、次に読んでいる間に何か方法が必要です。selectこのように:(擬似コード

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

私はパッケージossyncのドキュメントを検討し、これまでのところ、私はそれを行うにはどのような方法が表示されません。メイン、その読者に伝えるための

を終了しました> -

  • リーダーの読み取りを停止するためにそれを伝えるために>リーダー、 -

    1. メイン:私は信じて

  • +0

    実際にファイルを閉じる必要はありますか?最も安全な方法は、プロセスが終了するまで読書ゴルーチンを放置することです。 – JimB

    +0

    私は、異なるスレッド実行時にファイルハンドルを使用する理由、またはすべてのリソースに関して、なぜ使用する必要があるのか​​わかりません。それはあなたのコードを複雑にするだけです。 – Ankur

    +0

    @JimB、私は再接続を実装するためにファイルを閉じる必要があります。ノードが '/ dev/ttyUSB0'だったデバイスのプラグを抜いてファイルを閉じないと、'/dev/ttyUSB0'ファイルが開かれ、デバイスを接続すると、 '/ dev/ttyUSB1'です。もう一度 '/ dev/ttyUSB0'にする必要があります。 –

    答えて

    -1

    あなたは2つの信号を必要としますもちろん、あなたが好むシグナルシグナリングプリミティブ(チャネル、待機グループ、コンテキストなど)を選択できます。

    以下の例では、私はwaitgroupとcontextを使用します。理由は複数のリーダーを回転させることができ、すべてのリーダーを停止させるためにコンテキストを閉じるだけでよいという理由が です。

    と同じように複数のgoルーチンを作成しましたが、複数の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) 
         } 
        } 
    } 
    
    +1

    受信データのない読み取りは無期限にブロックされます。これは、読み取り呼び出しを中断することはできません。 – JimB

    +0

    あなたが正しいかもしれませんが、デフォルトの代わりにch <-f.Read(b)に変更するかどうかを確認します: – ahmy

    +0

    Readが最初に評価されるので、それでも何も変わりません。あなたは単にGoのファイル上で直接Readを中断することはできません。 – JimB

    関連する問題