QSerialPort适应多线程应用的改进
2015-11-22 19:51
330 查看
类Unix系统的设备接口使用了基于select的事件驱动,这使得设备对象必须存在于某一个线程中,而因为select事件无法直接从设备跨线程传输,双工设备的跨线程操作也无法直接实现。Qt作为跨平台的开发库,为兼容类Unix系统的事件驱动,也设计为类似的限制。
对于全双工串口的QSerialPort对象来说,当数据传输压力较小时,直接在主线程中通过为readyRead信号编写槽函数可以很方便的实现数据接收,而主线程中与UI互动相关的数据发送工作也可以很好的执行;而当某一方向的数据传输压力较大时,由于主线程还必须响应UI的事件循环,这可能会造成该方向的数据传输产生延迟,因此必须移入子线程以避开主线程的UI事件循环,也就是说,双工设备的跨线程操作是必要的。
那么如何设计基于Qt实现双工设备的跨线程操作呢?以串口为例,可以考虑使用Qt的阻塞式跨线程信号槽连接机制,在操作发起线程中发射信号传入参数后等待槽函数执行完成;而在串口的信号连接的槽函数中调用对应的操作函数,并将函数结果存入类成员变量作为中转,然后返回;操作发起线程阻塞结束后,通过读取类成员变量来获取并输出操作结果。
以串口的write操作示例具体的实现方法,新的方法命名为threadSafeWrite,而从QSerialPort继承的自定义类命名为QThreadSafeSerialPort:
由于使用了信号槽机制,QThreadSafeSerialPort 对象所在的线程必须使用事件循环,否则跨线程的threadSafeWrite调用仍然无法正确执行;而为了执行事件循环,QThread类的run函数中必须执行exec();exec()在事件循环结束之前不会返回,这使得在run函数中执行阻塞式数据接收或发送的方案变得无法实现,因此仍然不得不通过实现槽函数并连接readyRead/bytesWritten信号来实现数据接收/发送。为了让数据接收/发送的槽函数在子线程中执行,槽函数所依附的QObject继承类对象必须生存在子线程中。示例代码如下:
性能分析:虽然子线程中使用了事件循环,但由于子线程中并没有UI,不用执行UI事件,子线程所有的事件循环均可用于串口的通讯事件,因而子线程中的性能损失并不明显;跨线程的threadSafeWrite调用由于以阻塞式的信号连接方式连接信号和槽函数,需要跨线程等待事件循环的执行,其执行效率必然较为低下,这是使用跨线程信号槽机制所必须付出的代价;若threadSafeWrite的实现方案无法满足性能要求,则必须重新设计数据读写方案了。
对于全双工串口的QSerialPort对象来说,当数据传输压力较小时,直接在主线程中通过为readyRead信号编写槽函数可以很方便的实现数据接收,而主线程中与UI互动相关的数据发送工作也可以很好的执行;而当某一方向的数据传输压力较大时,由于主线程还必须响应UI的事件循环,这可能会造成该方向的数据传输产生延迟,因此必须移入子线程以避开主线程的UI事件循环,也就是说,双工设备的跨线程操作是必要的。
那么如何设计基于Qt实现双工设备的跨线程操作呢?以串口为例,可以考虑使用Qt的阻塞式跨线程信号槽连接机制,在操作发起线程中发射信号传入参数后等待槽函数执行完成;而在串口的信号连接的槽函数中调用对应的操作函数,并将函数结果存入类成员变量作为中转,然后返回;操作发起线程阻塞结束后,通过读取类成员变量来获取并输出操作结果。
以串口的write操作示例具体的实现方法,新的方法命名为threadSafeWrite,而从QSerialPort继承的自定义类命名为QThreadSafeSerialPort:
class QThreadSafeSerialPort : public QSerialPort { Q_OBJECT Q_DISABLE_COPY(QThreadSafeSerialPort) QMutex m_mutex; qint64 m_resInt64; Q_SIGNALS: void sigWrite(char const* data); public: QThreadSafeSerialPort(QObject* parent = Q_NULLPTR) : QSerialPort(parent) { connect(this, &sigWrite, this, [&](char const* data){m_resInt64 = write(data);}, Qt::BlockingQueuedConnection); } qint64 threadSafeWrite(char const* data) { if (QThread::currentThread() == thread()) { return write(data); } else { QMutexLocker locker(&m_mutex); emit sigWrite(data); return m_resInt64; } }
由于使用了信号槽机制,QThreadSafeSerialPort 对象所在的线程必须使用事件循环,否则跨线程的threadSafeWrite调用仍然无法正确执行;而为了执行事件循环,QThread类的run函数中必须执行exec();exec()在事件循环结束之前不会返回,这使得在run函数中执行阻塞式数据接收或发送的方案变得无法实现,因此仍然不得不通过实现槽函数并连接readyRead/bytesWritten信号来实现数据接收/发送。为了让数据接收/发送的槽函数在子线程中执行,槽函数所依附的QObject继承类对象必须生存在子线程中。示例代码如下:
class ThreadSlotHelper : public QObject { Q_OBJECT public: ThreadSlotHelper(void) : QObject(Q_NULLPTR) {} public Q_SLOTS: void onReadyRead(void) { // do read work... } }; class ExampleThread : public QThread { Q_OBJECT ThreadSlotHelper m_helperObj; QThreadSafeSerialPort m_port; protected: virtual void run(void) { m_port.open(QIODevice::ReadWrite); exec(); m_port.close(); } public: ExampleThread(QObject* parent = 0) : QThread(parent){ m_helperObj.moveToThread(this); m_port.moveToThread(this); connect(&m_port, SIGNAL(readyRead()), &m_helperObj, SLOT(onReadyRead())); } };
性能分析:虽然子线程中使用了事件循环,但由于子线程中并没有UI,不用执行UI事件,子线程所有的事件循环均可用于串口的通讯事件,因而子线程中的性能损失并不明显;跨线程的threadSafeWrite调用由于以阻塞式的信号连接方式连接信号和槽函数,需要跨线程等待事件循环的执行,其执行效率必然较为低下,这是使用跨线程信号槽机制所必须付出的代价;若threadSafeWrite的实现方案无法满足性能要求,则必须重新设计数据读写方案了。
相关文章推荐
- Python3写爬虫(四)多线程实现数据爬取
- QT学习 第一章:基本对话框
- 使用Shiboken为C++和Qt库创建Python绑定
- C#实现多线程的同步方法实例分析
- 浅谈chuck-lua中的多线程
- C#简单多线程同步和优先权用法实例
- C#多线程学习之(四)使用线程池进行多线程的自动管理
- C#多线程编程中的锁系统(三)
- C#多线程学习之(六)互斥对象用法实例
- 基于一个应用程序多线程误用的分析详解
- C#多线程学习之(三)生产者和消费者用法分析
- C#多线程学习之(一)多线程的相关概念分析
- C#多线程之Thread中Thread.IsAlive属性用法分析
- C#多线程编程之使用ReaderWriterLock类实现多用户读与单用户写同步的方法
- C#控制台下测试多线程的方法
- Ruby 多线程的潜力和弱点分析
- C#中WPF使用多线程调用窗体组件的方法
- C#如何对多线程、多任务管理(demo)
- C#实现多线程的Web代理服务器实例
- c#实现多线程局域网聊天系统