QSerialPort适应多线程应用的改进

类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的实现方案无法满足性能要求,则必须重新设计数据读写方案了。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值