You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何正确使用同步机制解决Go串口读取的goroutine冲突问题

解决Go语言串口读取时Goroutine同步冲突问题

咱们先拆解下你遇到的问题:两个goroutine同时读取同一个串口和Scanner,导致输出乱码,而且WaitGroup的用法也没达到你想要的“依次执行”效果。我来一步步帮你理清问题,再给出可行的解决方案。

为啥会出现数据混乱?

  1. 共享Scanner的致命问题:bufio.Scanner本身不是并发安全的,它内部维护着缓冲区和读取状态,多个goroutine同时调用Scan()会直接破坏这些内部状态,把数据拆得七零八落,这就是你看到乱码的根本原因。
  2. 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()
}

代码说明

  1. 单一读取goroutine:readLines是唯一操作串口和Scanner的goroutine,避免了并发读取的冲突,所有行数据都会被有序发送到lineChan。
  2. 通道控制执行顺序:nextChan用来传递“可以开始执行”的信号,第一个任务拿到信号后读取3行,完成后把信号发回通道,让第二个任务执行,以此循环,完美实现“依次执行”的需求。
  3. 资源安全:加入defer port.Close()确保程序退出时关闭串口,避免资源泄漏;通道关闭时会自动通知所有任务退出,不会出现goroutine泄漏。

这样修改后,输出就会和你预期的一致,再也不会出现乱码问题啦。

内容的提问来源于stack exchange,提问作者Melissa

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.29 08:44:21