Loading... # 一、thread::run 与 thread::start 在Qt中,`QObject::MoveToThread`与`Thread::Start`代表了两种不同的创建子线程的方式。Qt 5 官方文档中对两个函数说明如下: > **void** QObject::**moveToThread**(QThread* targetThread ) > > The starting point for the thread. After calling [start](https://doc.qt.io/qt-5/qthread.html#start)(), the newly created thread calls this function. The default implementation simply calls [exec](https://doc.qt.io/qt-5/qthread.html#exec)(). > > You can reimplement this function to facilitate advanced thread management. Returning from this method will end the execution of the thread. > **void** QThread::**start**[QThread::Priority] > > Begins execution of the thread by calling [run](https://doc.qt.io/qt-5/qthread.html#run)(). The operating system will schedule the thread according to the *priority* parameter. If the thread is already running, this function does nothing. > > The effect of the *priority* parameter is dependent on the operating system's scheduling policy. In particular, the *priority* will be ignored on systems that do not support thread priorities (such as on Linux, see the [sched_setscheduler](http://linux.die.net/man/2/sched_setscheduler) documentation for more details). 对两种创建线程方式说明如下: ## 1.use worker objects by moving them to the thread using QObject::moveToThread() ```c++ class Worker : public QObject { Q_OBJECT public slots: void doWork(const QString ¶meter) { QString result; /* ... here is the expensive or blocking operation ... */ emit resultReady(result); } signals: void resultReady(const QString &result); }; class Controller : public QObject { Q_OBJECT QThread workerThread; public: Controller() { Worker *worker = new Worker; worker->moveToThread(&workerThread); connect(&workerThread, &QThread::finished, worker, &QObject::deleteLater); connect(this, &Controller::operate, worker, &Worker::doWork); connect(worker, &Worker::resultReady, this, &Controller::handleResults); workerThread.start(); } ~Controller() { workerThread.quit(); workerThread.wait(); } public slots: void handleResults(const QString &); signals: void operate(const QString &); }; ``` ## 2.subclass QThread and reimplement run() ```C++ class WorkerThread : public QThread { Q_OBJECT void run() override { QString result; /* ... here is the expensive or blocking operation ... */ emit resultReady(result); } signals: void resultReady(const QString &s); }; void MyObject::startWorkInAThread() { WorkerThread *workerThread = new WorkerThread(this); connect(workerThread, &WorkerThread::resultReady, this, &MyObject::handleResults); connect(workerThread, &WorkerThread::finished, workerThread, &QObject::deleteLater); workerThread->start(); } ``` ## 3.两者的区别 ### (1)MoveToThread 调用`MoveToThread`将 **Changes the thread affinity for this object and its children. The object cannot be moved if it has a parent. Event processing will continue in the targetThread.**(原谅语文水平不行,无法用中文准确表达原文意思) 能有效避免类对象与多线程任务不属于同一线程而导致的问题。 需要注意的点: 1. 不能将父类不为空的对象实体移动到`QThread`; 2. 不能将`Widgets`移动到新线程中; 具体源码如下: ```C++ void QObject::moveToThread(QThread *targetThread) { Q_D(QObject); //条件判断 if (d->threadData->thread == targetThread) { // object is already in this thread return; } if (d->parent != 0) { //父类不为空,无法移动 qWarning("QObject::moveToThread: Cannot move objects with a parent"); return; } if (d->isWidget) { //对象为 widget,无法移动 qWarning("QObject::moveToThread: Widgets cannot be moved to a new thread"); return; } //当前QThreadData及目标QThreadData获取 QThreadData *currentData = QThreadData::current(); QThreadData *targetData = targetThread ? QThreadData::get2(targetThread) : nullptr; if (d->threadData->thread == 0 && currentData == targetData) { // one exception to the rule: we allow moving objects with no thread affinity to the current thread currentData = d->threadData; } else if (d->threadData != currentData) { qWarning("QObject::moveToThread: Current thread (%p) is not the object's thread (%p).\n" "Cannot move to target thread (%p)\n", currentData->thread.load(), d->threadData->thread.load(), targetData ? targetData->thread.load() : nullptr); #ifdef Q_OS_MAC qWarning("You might be loading two sets of Qt binaries into the same process. " "Check that all plugins are compiled against the right Qt binaries. Export " "DYLD_PRINT_LIBRARIES=1 and check that only one set of binaries are being loaded."); #endif return; } // prepare to move d->moveToThread_helper(); if (!targetData) targetData = new QThreadData(0); QOrderedMutexLocker locker(¤tData->postEventList.mutex, &targetData->postEventList.mutex); // keep currentData alive (since we've got it locked) currentData->ref(); // move the object //移动 d_func()->setThreadData_helper(currentData, targetData); locker.unlock(); // now currentData can commit suicide if it wants to currentData->deref(); } ``` ### (2) QThread.start() Begins execution of the thread by calling run(). The operating system will schedule the thread according to the priority parameter. If the thread is already running, this function does nothing. ### (3) 问题 场景:需要创建一个新线程,去一直向串口发送数据。 当使用继承`QThread`并重写`run`函数的方式时,若采用如下写法,将导致跨线程调用QObject。 ```c++ //cmyserialport.h #ifndef CMYSERIALPORT_H #define CMYSERIALPORT_H #include <QObject> #include <QtSerialPort/qserialport.h> #include <QThread> class CMySerialPort:public QThread { public: CMySerialPort(); QSerialPort *m_serialPort; QString QByteToHexString(QByteArray data_); void run(); }; #endif // CMYSERIALPORT_H //cmyserialport.cpp #include "cmyserialport.h" #include <QtSerialPort/qserialportinfo.h> #include <QtSerialPort/qserialport.h> #include <qdebug.h> CMySerialPort::CMySerialPort() { qDebug()<<"12122121"; m_serialPort = new QSerialPort; m_serialPort->setPortName("COM3");///dev/ttySZ3 if(!m_serialPort->open(QIODevice::ReadWrite))//用ReadWrite 的模式尝试打开串口 { qDebug()<<"打开失败!"; return; } qDebug()<<"串口打开成功!"; m_serialPort->setBaudRate(QSerialPort::Baud2400,QSerialPort::AllDirections);//设置波特率和读写方向 m_serialPort->setDataBits(QSerialPort::Data8); //数据位为8位 m_serialPort->setFlowControl(QSerialPort::NoFlowControl);//无流控制 m_serialPort->setParity(QSerialPort::EvenParity); //无校验位 m_serialPort->setStopBits(QSerialPort::OneStop); //一位停止位 connect(m_serialPort,&QSerialPort::readyRead,this,[=](){ auto bytes = m_serialPort->readAll(); qDebug()<<"recv:"<<QByteToHexString(bytes); }); char data[]= {0x68,0x58,0x21,0x11,0x01,0x02,0x35,0x68,0x11,0x04,0x33,0x32,0x34,0x35,0x75,0x16}; m_serialPort->write(data,16); } QString CMySerialPort::QByteToHexString(QByteArray data_) { QString ret(data_.toHex().toUpper());//转为16进制大写 int len = ret.length() / 2; for (int i = 1; i < len; i++) { ret.insert(2 * i + i - 1, " ");//编写格式 } return ret; } void CMySerialPort::run() { for(auto i=0;i<1000000;i++) { char data[]= {0x68,0x58,0x21,0x11,0x01,0x02,0x35,0x68,0x11,0x04,0x33,0x32,0x34,0x35,0x75,0x16}; m_serialPort->write(data,16); sleep(1); } } //main.cpp #include <QCoreApplication> #include "cmyserialport.h" int main(int argc, char *argv[]) { QCoreApplication a(argc, argv); CMySerialPort port; port.start(); return a.exec(); } ``` 其中一种解决方式是,将`m_serialPort`初始化放到`run`函数中。 但是当类结构复杂以后,全部初始化过程堆在`run`函数中对于**类的设计和使用会造成巨大困扰**,因此可采用继承`QObject`并调用`MoveToThread`的方式来解决**跨线程调用`QObject`对象**的问题。 上述代码,同样也埋下了一个巨大的隐患,这个我们在下文中解决。 # 二、QThread的休眠与事件循环 ## 1.问题描述 当我想在多线程任务中间隔执行任务,又不过多占用CPU资源时,可考虑使用`QThread::sleep()`、`QThread::msleep()`和`QThread::usleep()`。 Qt官方文档中有这么一段话: > Forces the current thread to sleep for *secs* seconds/milliseconds/microseconds/. > > Avoid using this function if you need to wait for a given condition to change. Instead, connect a slot to the signal that indicates the change or use an event handler (see [QObject::event](https://doc.qt.io/qt-5/qobject.html#event)()). > > Note: This function does not guarantee accuracy. The application may sleep longer than *secs* under heavy load conditions. **当你需要等待事件响应时,应当避免使用这个函数。** 而我们在前文中代码,就犯了这样一个致命错误:需要响应系统串口数据到达信号,但是由于调用了`sleep(1)`,导致该线程大部分时间处于休眠状态。 **从而无法及时接收串口数据。** ## 2.解决 该问题,可使用QEventLoop来解决。 > The QEventLoop class provides a means of entering and leaving an event loop. > ### **int** QEventLoop::**exec**([QEventLoop::ProcessEventsFlags](https://doc.qt.io/qt-5/qeventloop.html#ProcessEventsFlag-enum) *flags* = AllEvents) > > Enters the main event loop and waits until [exit](https://doc.qt.io/qt-5/qeventloop.html#exit)() is called. Returns the value that was passed to [exit](https://doc.qt.io/qt-5/qeventloop.html#exit)(). > > If *flags* are specified, only events of the types allowed by the *flags* will be processed. > > It is necessary to call this function to start event handling. The main event loop receives events from the window system and dispatches these to the application widgets. > > Generally speaking, no user interaction can take place before calling exec(). As a special case, modal widgets like [QMessageBox](https://doc.qt.io/qt-5/qmessagebox.html) can be used before calling exec(), because modal widgets use their own local event loop. > > To make your application perform idle processing (i.e. executing a special function whenever there are no pending events), use a [QTimer](https://doc.qt.io/qt-5/qtimer.html) with 0 timeout. More sophisticated idle processing schemes can be achieved using [processEvents](https://doc.qt.io/qt-5/qeventloop.html#processEvents)(). 因此可用以下代码来代替`sleep()`、`msleep()`和`usleep()`。 ```c++ void EventSleep(uint32_t msSec) { QEventLoop loop; QTimer::singleShot(msSec, &loop, SLOT(quit())); loop.exec(); } ``` ## 3.应用范围 对于需要响应系统消息的线程函数中,延时函数强烈建议使用上述`EventSleep`。 最后修改:2021 年 09 月 01 日 04 : 48 PM © 允许规范转载