Multithreading 以跨平台方式在Go中并发读取/关闭
我最近意识到,我不知道如何正确地Multithreading 以跨平台方式在Go中并发读取/关闭,multithreading,go,concurrency,race-condition,goroutine,Multithreading,Go,Concurrency,Race Condition,Goroutine,我最近意识到,我不知道如何正确地阅读和关闭。在我的特殊情况下,我需要使用串行端口来实现这一点,但问题更一般 如果我们不做任何额外的努力来同步事情,它会导致竞争条件。简单的例子: package main import ( "fmt" "os" "time" ) func main() { f, err := os.Open("/dev/ttyUSB0") if err != nil { panic(err) } //
阅读和关闭。在我的特殊情况下,我需要使用串行端口来实现这一点,但问题更一般
如果我们不做任何额外的努力来同步事情,它会导致竞争条件。简单的例子:
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唤醒时被锁定
我想手动实现类似的东西,但我需要一些方法来在阅读时选择东西。像这样:(伪代码)
选择{
案例b:=我相信你需要两个信号:
main->reader,告诉它停止阅读
reader->main,告知读卡器已终止
当然,您可以选择您喜欢的go信令原语(通道、waitgroup、上下文等)
下面的例子中,我使用了waitgroup和context
您可以旋转多个读卡器,只需关闭上下文即可告诉所有读卡器停止
我创建了多重围棋程序,就像
一个你甚至可以用它来协调多个围棋程序的例子
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)
}
}
}
主程序包
进口(
“上下文”
“fmt”
“操作系统”
“同步”
“时间”
)
func main(){
ctx,cancelFn:=context.WithCancel(context.Background())
f、 错误:=os.Open(“/dev/ttyUSB0”)
如果错误!=零{
恐慌(错误)
}
var wg sync.WaitGroup
对于i:=0;i<3;i++{
工作组.添加(1)
//启动一个goroutine,该goroutine保持从串行端口读取数据
go func(i int){
推迟工作组完成()
读卡器(ctx,f)
fmt.Printf(“读卡器%d已关闭\n”,i)
}(一)
}
时间。睡眠(1000*时间。毫秒)
fmt.Println(“交割”)
cancelFn()//通知所有读卡器停止
wg.Wait()//等待所有读卡器完成
f、 关闭()
fmt.Println(“文件关闭”)
时间。睡眠(1000*时间。毫秒)
}
func读取器(ctx context.context,f*os.File){
b:=make([]字节,100)
为了{
挑选{
您是否确实需要关闭该文件?最安全的方法是在进程退出之前保留读取goroutine。我不明白您为什么要在不同的执行线程或任何资源之间使用文件句柄。这只会使代码变得复杂。@JimB,我确实需要关闭该文件以实现重新连接:例如,当我拔下节点为/dev/ttyUSB0
的设备,但我没有关闭该文件时,该文件/dev/ttyUSB0
仍将打开,当我重新插入该设备时,它将成为/dev/ttyUSB1
。我需要它再次成为/dev/ttyUSB0
。@DmitryFrank:如果删除该设备没有取消对读取的阻止,ho你知道这个设备已经被删除了吗?@ JimB,好的,这是一个坏例子:在这个特殊的情况下,代码> Read < /Cord>将返回<代码> IO。EOF 确实,但是问题更一般。例如,考虑GROUTIN,它从串口读取数据,并将接收到的数据发送到某个通道,并且用户有“断开”。按钮,该按钮显然应该关闭文件,并在不进行争用的情况下执行该操作。没有传入数据的读取将无限期阻塞。这不能中断读取调用。您可能是对的,我将查看我是否将其更改为ch,这仍然不会更改任何内容,因为读取是首先计算的。您不能在运行时直接中断文件的读取(目前)。
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)
}
}
}