Qt信号槽传递大对象引用的可行性及线程安全问题咨询
嗨,我来帮你把这个问题掰扯清楚~
首先,你现在的写法确实会有很大概率导致程序崩溃或者出现未定义行为,原因得从Qt跨线程信号槽的机制说起:
你把MySocket移到了工作线程,所以sendData信号和onSendData槽之间的连接默认是队列连接(Queued Connection)。这种连接下,信号的参数会先存到主线程的事件队列里,等主线程的事件循环轮到它时才会执行槽函数。但你信号里传的是onReadyRead里的局部变量datagram的引用——这个局部变量在onReadyRead函数执行结束后就会被销毁,内存被释放,等主线程执行槽函数时,引用指向的内存已经不存在了,这时候操作这个引用必然出问题。
那该怎么解决?给你两个靠谱的方案:
方案一:利用Qt隐式共享传递值(最推荐)
QByteArray是Qt里支持隐式共享的类型,简单说就是传递值的时候不会真的拷贝整个大数组,只是拷贝一个轻量的“数据句柄”(包含指针和引用计数),性能开销几乎可以忽略。你只需要把信号和槽的参数改成值传递即可:
// 信号定义改成 void sendData(QByteArray data); // 槽函数改成 void Widget::onSendData(QByteArray data) { // 放心处理data就行,底层会自动管理数据生命周期 }
这样不管是跨线程还是同线程,都完全安全,而且对大对象来说效率也很高。
方案二:手动管理数据生命周期(针对非隐式共享类型)
如果是你自己定义的不支持隐式共享的大对象,那可以用智能指针来传递,比如std::shared_ptr<QByteArray>,这样只要槽函数还在使用数据,内存就不会被释放:
// 信号定义 void sendData(std::shared_ptr<QByteArray> data); // 工作线程里的代码 void MySocket::onReadyRead() { auto datagram = std::make_shared<QByteArray>(); datagram->resize(m_socket->pendingDatagramSize()); // 读取数据到datagram里 emit sendData(datagram); } // 主线程槽函数 void Widget::onSendData(std::shared_ptr<QByteArray> data) { // 处理data,不用手动释放 }
不过对于QByteArray来说,完全没必要这么折腾,方案一就足够了。
另外再提一句:有人说“可以”的情况,可能是指直接连接(Direct Connection)——这种连接下槽函数会在发送信号的工作线程里立即执行,这时候局部变量还在作用域内,引用是有效的。但直接连接跨线程操作GUI的话,Qt是不允许的(GUI必须在主线程操作),而且如果槽函数处理时间长,会阻塞你的工作线程,所以这种方案不可取。
附上你提供的代码片段以便对照:
// GUI thread Widget::Widget(QWidget *parent) : QWidget(parent) , ui(new Ui::Widget) { ui->setupUi(this); m_thread = new QThread(this); m_socket = new MySocket(); connect(m_socket,&MySocket::sendData,this,&Widget::onSendData); m_socket->moveToThread(m_thread); m_thread->start(); } void Widget::onSendData(QByteArray &data) { // to do something } // work thread void MySocket::onReadyRead() { QByteArray datagram; datagram.resize(m_socket->pendingDatagramSize()); QHostAddress peerAddr...
备注:内容来源于stack exchange,提问作者liuhy

