您的位置:首页 > 编程语言 > Qt开发

QSerialPort适应多线程应用的改进

2015-11-22 19:51 330 查看
类Unix系统的设备接口使用了基于select的事件驱动,这使得设备对象必须存在于某一个线程中,而因为select事件无法直接从设备跨线程传输,双工设备的跨线程操作也无法直接实现。Qt作为跨平台的开发库,为兼容类Unix系统的事件驱动,也设计为类似的限制。

对于全双工串口的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的实现方案无法满足性能要求,则必须重新设计数据读写方案了。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签:  Qt 多线程 serialport