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

Go语言用goroutines解析串口输入无输出问题排查与优化咨询

问题背景

我用Go语言写了一段代码,想通过串口读取Arduino输出的数据,并用goroutine解析输出。原代码如下:

package main 
import ( 
	"log" 
	"github.com/tarm/serial" 
	"bufio" 
	"sync" 
	"fmt" 
) 

func readFirstLine(data []string, wg *sync.WaitGroup){ 
	defer wg.Done() 
	fmt.Printf("This is the sensor data:\n%q\n%q", data[0], data[1]) 
} 

func readSecondLine(data []string, wg *sync.WaitGroup){ 
	defer wg.Done() 
	fmt.Printf("This is the actuator data:\n%q", data[2]) 
} 

func main() { 
	usbRead := &serial.Config{Name: "COM5", Baud: 9600, ReadTimeout: 0} 
	port, err := serial.OpenPort(usbRead) 
	var wg sync.WaitGroup 
	wg.Add(2) 
	if err != nil { 
		log.Fatal(err) 
	} 
	data := []string{} 
	scanner := bufio.NewScanner(port) 
	for scanner.Scan() { 
		data = append(data, scanner.Text()) 
	} 
	for { 
		go readFirstLine(data, &wg) 
		go readSecondLine(data, &wg) 
		wg.Wait() 
	} 
}

Arduino串口循环输出的内容是:

{"temperature":[27.7],"humidity":[46.9],"sensor":"DHT22"} 
{"temperature":[25.41545],"sensor":"LM35DZ"} 
{"blink":["true"],"actuator":"arduinoLED"}

我期望的输出效果是循环打印:

This is the sensor data: {"temperature":[27.7],"humidity":[46.9],"sensor":"DHT22"} {"temperature":[25.41545],"sensor":"LM35DZ"} 
This is the actuator data: {"blink":["true"],"actuator":"arduinoLED"}

但现在程序完全没有输出,我推测是数据存储方式的问题,请问该怎么修复?另外修复后这种goroutine的使用方式是否符合我的需求?


问题诊断与修复

首先咱们来拆解下代码里的核心问题:

  • 阻塞的Scanner循环:你写的for scanner.Scan()会一直持续读取串口数据,直到串口断开或者出错,后面的那个无限for {}循环根本没机会执行,这就是程序没有任何输出的直接原因。
  • 数据收集逻辑错位:你打算等所有串口数据都读完再处理,但实际上串口是持续循环输出那三行数据的,这种收集方式完全不符合实时处理的需求。
  • WaitGroup使用不当:你把wg.Add(2)放在了scanner.Scan()循环之前,后续无限循环里重复启动goroutine却没有重新添加计数,会导致WaitGroup的计数混乱,甚至引发panic。

修复后的完整代码

下面是调整后的代码,关键位置加了注释说明:

package main

import (
	"bufio"
	"fmt"
	"log"
	"sync"

	"github.com/tarm/serial"
)

// 处理传感器数据的goroutine函数,直接传入需要的两行数据
func processSensorData(dhtData, lm35Data string, wg *sync.WaitGroup) {
	defer wg.Done()
	fmt.Printf("This is the sensor data:\n%s\n%s\n", dhtData, lm35Data)
}

// 处理执行器数据的goroutine函数,传入对应的行数据
func processActuatorData(ledData string, wg *sync.WaitGroup) {
	defer wg.Done()
	fmt.Printf("This is the actuator data:\n%s\n\n", ledData)
}

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() // 确保程序退出时自动关闭串口

	scanner := bufio.NewScanner(port)
	dataBuffer := make([]string, 0, 3) // 固定容量的缓冲区,专门存一轮的3行数据

	for scanner.Scan() {
		line := scanner.Text()
		dataBuffer = append(dataBuffer, line)

		// 当凑齐3行完整数据时,启动goroutine处理
		if len(dataBuffer) == 3 {
			var wg sync.WaitGroup
			wg.Add(2)

			// 直接传递数据副本,避免后续缓冲区清空影响goroutine
			go processSensorData(dataBuffer[0], dataBuffer[1], &wg)
			go processActuatorData(dataBuffer[2], &wg)

			wg.Wait() // 等待本次的两个goroutine处理完成,再接收下一轮数据
			dataBuffer = dataBuffer[:0] // 清空缓冲区,准备接收下一组3行数据
		}
	}

	// 处理scanner可能出现的错误
	if err := scanner.Err(); err != nil {
		log.Fatal(err)
	}
}

关于goroutine使用的合理性分析

你想用goroutine并行处理数据的思路是完全没问题的,是否符合需求可以分两种情况看:

  • 如果后续你需要对传感器数据做复杂的解析(比如JSON反序列化、数据校准、存储),或者对执行器数据做逻辑判断,用两个goroutine并行处理能有效提升效率,这种写法完全符合需求。
  • 如果只是简单的打印输出,并行和串行的差异不大,但这种写法也不会有性能损耗,反而能为后续的功能扩展预留空间。

另外要注意:

  • 我在代码里每次处理一组数据时创建新的WaitGroup,彻底避免了计数混乱的问题。
  • 直接传递数据副本给goroutine,而不是缓冲区引用,防止缓冲区被清空后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 09:09:00