Taskbus - 基于Qt的跨平台多进程合作框架(三)主要技术

上一篇文章中,我们介绍了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”, 迅速在不同的操作系统上,实现双机互联:

TCP吞吐

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,我们的界面具有非常好的工业效果。

带有内存监视的tasjBus

5 后记

taskBus 作为 Qt 教学的示范项目,与Simulink、GnuRadio、Labview相比,缺少大量的关键模块,且对模块的管理太过灵活与松散。尽管如此,她还是一个非常棒的开端!

发布了127 篇原创文章 · 获赞 330 · 访问量 48万+

猜你喜欢

转载自blog.csdn.net/goldenhawking/article/details/84403285
今日推荐