通过QueuedConnection传递QByteArray是否引发竞态及传参方式咨询
你的问题核心在于QByteArray的隐式共享写时拷贝机制和Qt QueuedConnection参数传递时机的交互冲突,下面一步步拆解并给出解决办法:
为什么会出现“旧信号收到新数据”的异常?
当你通过QueuedConnection发射信号时,Qt会对传递的QByteArray参数做一次浅拷贝(因为QByteArray是隐式共享类型),并把这个浅拷贝放进目标线程的事件队列。这个浅拷贝和原dp_v2_databuff_共享同一个底层数据块,此时原对象的引用计数会增加到2。
但如果在Qt完成参数拷贝的瞬间(比如极端情况下拷贝动作还没结束),你立刻调用了dp_v2_databuff_.append(),这时候原对象的引用计数可能还没来得及升到2,QByteArray就会直接修改共享的底层数据块——事件队列里等待的那个浅拷贝自然也会读到被修改后的数据,也就是你遇到的“类型为OEI_CBT_DOUBLE却带有时间戳”的异常。
你的两个疑问解答
QByteArray::append是否未做深拷贝?
不是的。append()的逻辑是:只有当对象的引用计数大于1时,才会触发深拷贝;如果引用计数为1,就直接修改原数据块。问题出在你第一次emit后,原对象的引用计数还没被Qt的参数拷贝提升,就立刻执行了append,导致共享数据被修改。
应该用const引用还是值传递?
对于QueuedConnection来说,两种方式最终都会触发参数拷贝(跨线程传递不能依赖原对象的生命周期),但更稳妥的选择是:
- 优先使用值传递(也就是你当前的
QByteArray data写法); - 或者使用
const QByteArray&作为参数(Qt会自动将其拷贝到事件队列,避免悬空引用风险)。
但无论哪种传参方式,要彻底解决问题,你需要确保第一次emit后,原对象的修改不会影响已经进入事件队列的参数。
可靠的解决方案
方案1:emit前显式创建独立拷贝
在第一次发射信号前,先把原数据复制一份独立的QByteArray,这样后续修改原对象不会影响这个拷贝:
// 第一次发射前创建独立拷贝,确保和原数据彻底分离 QByteArray firstData = dp_v2_databuff_; emit newData(OEI_DataParserV2_base::OEI_CBT_DOUBLE, firstData); // 之后再修改原数据 uint32_t sec; uint32_t usec; dataParserV2->getDataTimestamp(sec, usec); dp_v2_databuff_.append(reinterpret_cast<const char*>(&usec), sizeof(usec)); dp_v2_databuff_.append(reinterpret_cast<const char*>(&sec), sizeof(sec)); emit newData(OEI_DataParserV2_base::OEI_CBT_TIMESTAMP_DOUBLE, dp_v2_databuff_);
方案2:emit后强制触发深拷贝
在第一次emit后,调用detach()强制原对象生成独立的数据块,后续的append就不会影响事件队列里的参数:
emit newData(OEI_DataParserV2_base::OEI_CBT_DOUBLE, dp_v2_databuff_); dp_v2_databuff_.detach(); // 强制深拷贝,让原对象拥有独立数据块 // 之后修改数据 uint32_t sec; uint32_t usec; dataParserV2->getDataTimestamp(sec, usec); dp_v2_databuff_.append(reinterpret_cast<const char*>(&usec), sizeof(usec)); dp_v2_databuff_.append(reinterpret_cast<const char*>(&sec), sizeof(sec)); emit newData(OEI_DataParserV2_base::OEI_CBT_TIMESTAMP_DOUBLE, dp_v2_databuff_);
补充说明
Qt文档提到“跨线程共享隐式共享类实例需加锁”,指的是主动在多个线程中直接访问同一个实例的场景;而QueuedConnection的信号槽机制已经帮你处理了参数拷贝,所以不需要额外加锁——只要避免emit后立即修改原对象导致的共享数据冲突即可。
内容的提问来源于stack exchange,提问作者Elwood

