Go中的并发读取/关闭,跨平台方式

时间:2017-01-18 00:01:38

标签: multithreading go concurrency race-condition goroutine

我最近意识到我并不知道如何在Go中同时正确ReadClose。在我的特定情况下,我需要使用串行端口,但问题更通用。

如果我们在没有任何额外努力同步事情的情况下这样做,就会导致竞争条件。简单的例子:

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

我检查过软件包ossync的文档,到目前为止,我还没有看到任何方法。

1 个答案:

答案 0 :(得分:-1)

我相信你需要2个信号:

  1. main - &gt;读者,告诉它停止阅读
  2. 读者 - &gt;主要,告诉读者已被终止
  3. 当然你可以选择你喜欢的去信令原语(频道,等候组,上下文等)。

    下面的示例,我使用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)
            }
        }
    }