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
相关产品推荐
相关产品推荐

