C++多线程向COM端口发送字节包失败问题咨询
多线程串口定时发送数据包的问题分析与优化方案
原代码里的几个坑
线程没管好
创建线程t1后没调用join()或detach(),这属于C++线程的违规写法——哪怕主线程是死循环,一旦程序触发退出逻辑,会直接触发std::terminate()崩掉。串口并发写入没同步
Windows串口句柄本身支持多线程调用WriteFile,但如果主线程也会往串口写数据,两个线程同时写会把数据包拆得七零八落,必须加锁保证同一时间只有一个线程操作串口。局部数组的生命周期隐患
bootPacket是main里的局部数组,传指针给线程后,要是哪天主线程提前退出了,数组会被销毁,线程再访问就是野指针,直接炸。cout的输出问题
虽然C++11后std::cout单操作是线程安全的,但调试环境下经常会遇到输出缓冲没刷新的情况,看起来像是没输出;而且多线程同时打日志会乱成一锅粥,最好加锁保护输出。
优化后的实现思路
1. 给线程加个“开关”
用std::atomic<bool>做线程运行的控制标志,代替死循环,这样主线程能优雅通知定时线程停止,不用硬杀。线程创建后调用detach()让它后台跑,或者在主线程退出前join()等它结束。
2. 串口操作加锁
用std::mutex把所有串口读写操作包起来,不管是定时线程的写,还是主线程的读写,都得先拿锁再操作,避免并发冲突。
3. 把数据包复制到线程内部
定时线程启动时,把传入的数据包复制一份到自己的局部变量里,彻底跟主线程的局部数组解绑,再也不用担心数组被销毁的问题。
4. 错误处理补全
串口初始化失败直接退出程序,别硬往下跑;每个Win32 API调用失败都输出错误码,方便排查问题。
修改后的代码示例
#include <iostream> #include <thread> #include <atomic> #include <mutex> #include <cstring> #include <windows.h> using byte = unsigned char; std::mutex serialMutex; std::atomic<bool> isRunning(true); void sendBootSignal(HANDLE h_Serial, const byte packetArr[], DWORD size) { DWORD dwWrite = 0; // 加锁保护串口写入操作 std::lock_guard<std::mutex> serialLock(serialMutex); if (!WriteFile(h_Serial, packetArr, size, &dwWrite, NULL)) { // 加锁保护cout输出,避免日志混乱 std::lock_guard<std::mutex> coutLock(serialMutex); std::cout << "WriteFile失败,错误码: " << GetLastError() << std::endl; } else { std::lock_guard<std::mutex> coutLock(serialMutex); std::cout << "Boot包发送成功,写入字节数: " << dwWrite << std::endl; } } void bootThread(HANDLE h_Serial, const byte packetArr[], DWORD size) { // 把数据包复制到线程内部,避免外部数组销毁 byte localPacket[11]; memcpy(localPacket, packetArr, size); while (isRunning.load(std::memory_order_acquire)) { sendBootSignal(h_Serial, localPacket, size); std::this_thread::sleep_for(std::chrono::milliseconds(50)); } } // 假设这两个函数是你已经实现的 void sizeCalculator(byte packet[], DWORD& size) {} void checksumCalculator(byte packet[], DWORD size) {} int main() { HANDLE h_Serial = CreateFile(L"COM3", GENERIC_READ | GENERIC_WRITE, 0, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL); if (h_Serial == INVALID_HANDLE_VALUE) { DWORD err = GetLastError(); if (err == ERROR_FILE_NOT_FOUND) { std::cout << "未找到串口COM3" << std::endl; } std::cout << "打开串口失败,错误码: " << err << std::endl; return 1; } DCB dcbSerialParam = { 0 }; dcbSerialParam.DCBlength = sizeof(dcbSerialParam); if (!GetCommState(h_Serial, &dcbSerialParam)) { std::cout << "获取串口状态失败,错误码: " << GetLastError() << std::endl; CloseHandle(h_Serial); return 1; } dcbSerialParam.BaudRate = CBR_115200; dcbSerialParam.ByteSize = 8; dcbSerialParam.StopBits = ONESTOPBIT; dcbSerialParam.Parity = NOPARITY; dcbSerialParam.fDtrControl = DTR_CONTROL_DISABLE; if (!SetCommState(h_Serial, &dcbSerialParam)) { std::cout << "设置串口状态失败,错误码: " << GetLastError() << std::endl; CloseHandle(h_Serial); return 1; } COMMTIMEOUTS timeout = { 0 }; timeout.ReadIntervalTimeout = 60; timeout.ReadTotalTimeoutConstant = 60; timeout.ReadTotalTimeoutMultiplier = 15; timeout.WriteTotalTimeoutConstant = 60; timeout.WriteTotalTimeoutMultiplier = 8; if (!SetCommTimeouts(h_Serial, &timeout)) { std::cout << "设置超时失败,错误码: " << GetLastError() << std::endl; CloseHandle(h_Serial); return 1; } byte bootPacket[11] = { 0x10, 0x00, 0x80, 0x00, 0x48, 0x00, 0xFF, 0x00, 0x00, 0x10, 0x03 }; DWORD size = 11; sizeCalculator(bootPacket, size); checksumCalculator(bootPacket, size); std::thread t1(bootThread, h_Serial, bootPacket, size); t1.detach(); // 让线程在后台运行 // 主线程处理其他业务逻辑,比如读取串口数据 while (isRunning.load(std::memory_order_acquire)) { byte readBuf[1024]; DWORD dwRead = 0; std::lock_guard<std::mutex> lock(serialMutex); if (ReadFile(h_Serial, readBuf, sizeof(readBuf), &dwRead, NULL) && dwRead > 0) { std::lock_guard<std::mutex> coutLock(serialMutex); std::cout << "收到" << dwRead << "字节数据" << std::endl; } // 让主线程稍微休眠,避免占用过多CPU std::this_thread::sleep_for(std::chrono::milliseconds(10)); } // 通知线程停止运行 isRunning.store(false, std::memory_order_release); CloseHandle(h_Serial); return 0; }
优化点说明
- 线程安全:用
std::mutex保护串口操作和日志输出,避免并发冲突和日志混乱。 - 优雅退出:通过原子变量控制线程循环,主线程退出前能通知定时线程正常结束。
- 生命周期安全:线程内部复制数据包,彻底消除局部变量销毁带来的野指针风险。
- 错误排查:每个Win32 API失败都输出错误码,方便快速定位问题;初始化失败直接退出,避免无效操作。
内容的提问来源于stack exchange,提问作者cptalpdeniz
相关产品推荐
相关产品推荐

