如何正确使用同步机制解决Go串口读取的goroutine冲突问题
解决Go语言串口读取时Goroutine同步冲突问题
咱们先拆解下你遇到的问题:两个goroutine同时读取同一个串口和Scanner,导致输出乱码,而且WaitGroup的用法也没达到你想要的“依次执行”效果。我来一步步帮你理清问题,再给出可行的解决方案。
为啥会出现数据混乱?
- 共享Scanner的致命问题:
bufio.Scanner本身不是并发安全的,它内部维护着缓冲区和读取状态,多个goroutine同时调用Scan()会直接破坏这些内部状态,把数据拆得七零八落,这就是你看到乱码的根本原因。 - WaitGroup用错了:你的两个goroutine里是无限循环的
Scan(),永远不会退出,所以defer wg.Done()永远不会执行,wg.Wait()其实会一直阻塞。而且你每次外层循环都启动两个新goroutine,等于不断创建新的竞争线程,乱上加乱。
正确的解决方案:串行读取+通道同步顺序
串口是单一流,不能并发读取,所以咱们得让一个goroutine专门负责读串口,然后用通道把数据传递给处理goroutine,再用另一个通道控制两个处理任务的执行顺序,确保一个做完再做另一个。
修改后的代码
package main import ( "bufio" "fmt" "log" "sync" "github.com/tarm/serial" ) // 专门负责读取串口所有行,发送到通道 func readLines(port *serial.Port, lineChan chan<- string) { scanner := bufio.NewScanner(port) for scanner.Scan() { lineChan <- scanner.Text() } if err := scanner.Err(); err != nil { log.Fatalf("串口读取失败: %v", err) } close(lineChan) } // 处理单个任务:读取3行数据并输出,完成后通知下一个任务 func processTask(taskName string, lineChan <-chan string, wg *sync.WaitGroup, nextChan chan<- struct{}) { defer wg.Done() for { // 等待前一个任务完成的信号 ready, ok := <-nextChan if !ok { // 通道关闭,退出循环 return } fmt.Printf("\n=== 执行%s ===\n", taskName) // 读取3行数据 for i := 0; i < 3; i++ { line, lineOk := <-lineChan if !lineOk { // 数据通道关闭,通知另一个任务退出 close(nextChan) return } fmt.Println(line) } // 发送信号,让下一个任务开始执行 nextChan <- ready } } func main() { usbRead := &serial.Config{Name: "COM5", Baud: 9600, ReadTimeout: 0} port, err := serial.OpenPort(usbRead) if err != nil { log.Fatal(err) } defer port.Close() // 程序退出时自动关闭串口,避免资源泄漏 // 缓冲通道,存储读取到的串口行数据 lineChan := make(chan string, 10) // 启动专门的读取goroutine go readLines(port, lineChan) var wg sync.WaitGroup // 用通道控制任务执行顺序,初始发送一个信号让第一个任务启动 nextChan := make(chan struct{}, 1) nextChan <- struct{}{} wg.Add(2) // 启动两个处理任务 go processTask("任务1", lineChan, &wg, nextChan) go processTask("任务2", lineChan, &wg, nextChan) // 等待所有任务结束 wg.Wait() }
代码说明
- 单一读取goroutine:
readLines是唯一操作串口和Scanner的goroutine,避免了并发读取的冲突,所有行数据都会被有序发送到lineChan。 - 通道控制执行顺序:
nextChan用来传递“可以开始执行”的信号,第一个任务拿到信号后读取3行,完成后把信号发回通道,让第二个任务执行,以此循环,完美实现“依次执行”的需求。 - 资源安全:加入
defer port.Close()确保程序退出时关闭串口,避免资源泄漏;通道关闭时会自动通知所有任务退出,不会出现goroutine泄漏。
这样修改后,输出就会和你预期的一致,再也不会出现乱码问题啦。
内容的提问来源于stack exchange,提问作者Melissa
相关产品推荐
相关产品推荐

