在上一篇文章中,我们介绍了taskBus的原理。最近这段时间,主框架仍旧在密集的迭代中。通过在Linux及win32下,实现内存监视,我们已经能够实时监视各个模块的资源使用。该项目相对于其功能来说,代码量并不大,主要是得益于Qt强大的现有资源。
文章目录
1. QIODevice类簇
taskBus 能够顺利吞吐数据的关键因素是强大的 QProcess 。我们仔细查看该类的文档,可以发现它派生自 QIODevice。这样就太棒了!因为 QIODevice有一整套信号与槽,用于数据吞吐。而 进程的管道,被其封装,使得我们在Linux、Windows下得到了非常一致的开发体验。
如果仔细查看文档,会发现进程、套接字、串口、缓冲、文件、媒体流,这些含有输入输出功能的重要要素,全部派生自QIODevice。我们很容易利用虚函数特性,采用基类接口同时操作上述对象。回到进程管理,代码中,taskNode::taskNode 是用于管理各个模块进程的类。这个类直接与QProcess的信号链接。
connect(m_process,&QProcess::readyReadStandardOutput,this,&taskNode::slot_readyReadStandardOutput,Qt::QueuedConnection);
connect(m_process,&QProcess::readyReadStandardError,this,taskNode::slot_readyReadStandardError,Qt::QueuedConnection);
connect(m_process,&QProcess::bytesWritten,this,&taskNode::slot_sended,Qt::QueuedConnection);
1.1 QIODevice子类
- QAbstractSocket
– QTcpSocket
— QSctpSocket
— QSslSocket
– QUdpSocket - QBuffer
- QFileDevice
– QFile
— QTemporaryFile
– QSaveFile - QLocalSocket
- QNetworkReply
- QProcess
- QSerialPort
1.2 TCP点对点连接
正是受惠于上述机制,我们的分布式点对点连接才变得得心应手。具体参考例子 “modules/network_p2p”, 迅速在不同的操作系统上,实现双机互联:
2. 隐式共享
隐式共享,是Qt中非常重要的特性。这个特性使得函数返回值、信号与槽可以使用庞大的复杂对象进行传递。以QByteArray举例,QByteArray带有Implicit Sharing (隐式共享),以便在不维护动态内存分配、释放的情况下,高效的传递大量数据。这个机制,使得1MB大小的内存块可以通过跨越线程的信号-槽连接,轻盈地传递。
2.1 与C++右值引用的区别
C++1X的右值引用也有异曲同工之妙,但仍旧不如隐式共享来的通透。右值引用一旦发生,原有的对象就作废了。其实质上是传递了内含指针。隐式共享,等效为“修改时复制”,各个不同线程的对象共享一个内存。只有当第一个非const方法被调用时,内存拷贝才发生。
2.2 放心大胆的使用复杂对象
有了上述机制,我们在信号与槽中、函数返回值中放心大胆的使用复杂对象。这是taskBus中非常常见的信号与槽沟通:
signals:
//进程消息 Process messages
void sig_new_package(QByteArray);
void sig_new_errmsg(QByteArray);
//...注意,信号与槽位于不同线程
connect (node, &taskNode::sig_new_package, this, &taskProject::slot_new_package,Qt::QueuedConnection);
connect (node, &taskNode::sig_new_errmsg, this, &taskProject::slot_new_errmsg,Qt::QueuedConnection);
//...
emit sig_new_package(arr);
如果没有隐式共享,则信号与槽的传递过程中,会发生大量内存拷贝。在数据IO密集时,CPU必然会很高。
2.3 注意事项
需要注意的是,复杂对象一定要在宽松的独立线程中处理,而后在拥挤的IO线程中传输。在taskBus中,复杂的处理主要集中在包的拼接上。我们把这部分放在线程池中完成,当包彻底组完,把整体的包一并emit出去。从发出去开始,直到交给消费者,包不会发生变化了。
我们在独立的线程池中拼接:
//...
taskNode * node = new taskNode(...);
node->moveToThread(threadpool()->idleThread());
//...
/*!
* \brief taskNode::slot_readyReadStandardOutput
* 为了后续的处理需要,这里会等到一个完整的包到来再发送。
* For subsequent processing needs,
* this will wait until a complete package arrives before sending.
*/
void taskNode::slot_readyReadStandardOutput()
{
QByteArray arred = m_process->readAllStandardOutput();
m_array_stdout.append(arred);
while (m_array_stdout.size()>=sizeof(TASKBUS::subject_package_header))
{
auto header = (header_type) (m_array_stdout.constData());
QByteArray arr(m_array_stdout.constData(), header->data_length);
emit sig_new_package(arr);
//...
}
}
上述代码调用了 constData,而不是 data。一旦data被调用,意味着要修改数据,会导致内存拷贝。
3. QJson与Qt容器
对Json文件的解析,是Qt的强大亮点。与XML模型不同,Qt对JSON的解析更加简便。taskBus的所有磁盘持久化内容全部为JSON. 以下面这个函数为例:
/*!
* \brief taskProject::fromJson Fill project from json
* \param json JSON project ,from file *.tbj
* \param m_pRefModel 场景中可用的模块列表。
* A list of modules available in the scene.
*/
void taskProject::fromJson(QByteArray json, taskCell * m_prefCell)
{
QJsonDocument doc = QJsonDocument::fromJson(json);
QJsonObject obj_root = doc.object();
if (obj_root.isEmpty())
return;
int insts = obj_root["total_mods"].toInt();
for(int i=0;i<insts;++i)
{
QString key = QString("mod%1").arg(i);
QJsonObject obj_cur = obj_root[key].toObject();
//...
double fx = obj_root[key].toObject()["x"].toDouble();
double fy = obj_root[key].toObject()["y"].toDouble();
/...
}
//...
}
QVariantMap 把C++静态语言模拟动态语言效果的工作几乎做到了极致。QVariantMap 的value还可以是QVariantMap ,结合 QSet、QHash、QMap、QList、QVector、QMultimap… 我们得以在不使用自定义struct\维护动态分配内存的情况下,实现非常复杂的数据结构。
参考 taskProject 的索引变量便可见一斑:
/*!
* 索引成员变量,会在refresh_idxes调用时生成
* Index member variables that is generated when refresh_idxes is called
*/
private:
QMap< unsigned int, int> m_idx_instance2vec;
QMap< taskNode * , int> m_idx_node2vec;
QMap< unsigned int, QVector<unsigned int> > m_idx_in2instances;
QMap< unsigned int, QVector<QString> > m_idx_in2names;
QMap< unsigned int, QVector<unsigned int> > m_idx_out2instances;
QMap< unsigned int, QVector<QString> > m_idx_out2names;
private:
QMap< unsigned int, unsigned int> m_hang_in2instance;
QMap< unsigned int, QString> m_hang_in2name;
QMap< unsigned int, QString> m_hang_in2fullname;
QMap< QString, unsigned int> m_hang_fullname2in;
QMap< unsigned int, unsigned int> m_hang_out2instance;
QMap< unsigned int, QString> m_hang_out2name;
QMap< unsigned int, QString> m_hang_out2fullname;
QMap< QString ,unsigned int> m_hang_fullname2out;
QMap< unsigned int, unsigned int> m_iface_inside2outside_in;
QMap< unsigned int, unsigned int> m_iface_inside2outside_out;
QMap< unsigned int, unsigned int> m_iface_outside2inside_in;
QMap< unsigned int, unsigned int> m_iface_outside2inside_out;
private:
std::function<taskCell * (void)> m_fNewCell;
std::function<void (taskCell * pmod, taskNode * pnod,QPointF pt)> m_fInsAppended;
std::function<void (void)> m_fIndexRefreshed;
std::function<QPointF (int)> m_fGetCellPos;
4. 高级GUI
Qt的Graphics-类簇,提供了非常便捷的模块可视化功能。除此以外,还有很多特性被采用。这里做一个罗列:
- 为了实现图形化的拖放功能,QGraphics类簇被引入。
- QtCharts 图表功能
- QDockWidgets
- 模型-视图结构的大量使用,使得维护数据集变得非常容易。
采用Qt,我们的界面具有非常好的工业效果。
5 后记
taskBus 作为 Qt 教学的示范项目,与Simulink、GnuRadio、Labview相比,缺少大量的关键模块,且对模块的管理太过灵活与松散。尽管如此,她还是一个非常棒的开端!