Qt|多线程线程池处理大量数据写入数据库

参考博客:QThread必须要了解的几个函数
https://blog.csdn.net/t46414704152abc/article/details/52155777
设计思路:读文件生成sql语句写入内存为一个线程,返回sql语句给主线程;主线程再调用写数据库操作线程。
其中包括:文件的读取;qt多线程;多线程访问数据库写入数据。
使用线程池读取大数据文件,测试通过。
测试使用的三个文件每个文件有32万多条数据,总计94万条数据,全部写入数据库预计需要三分钟时间。
整体结构:
在这里插入图片描述

ThreadPractice.h

// 使用线程池读取大数据文件,测试通过。
// 测试使用的三个文件每个文件有32万多条数据,总计94万条数据,全部写入数据库预计需要三分钟时间。
#pragma once
#pragma execution_character_set("utf-8")

#include <QWidget>
#include <QtCharts/qchartglobal.h>
#include <qthreadpool.h>
#include <qthread.h>
#include <qrunnable.h>
#include <queue>
#include <qmutex.h>
#include <QtSql/qtsqlglobal.h>
#include <qapplication.h>

// 保存数据库路径
static QString save_db_path;

// 读取文件线程 注册线程中处理函数,线程结束给主线程返回信号同时启动写数据库线程
class FileRunnable
	: public QObject
	, public QRunnable
{
	Q_OBJECT

public:
	explicit FileRunnable(QObject * parent = nullptr) {}
	~FileRunnable() {}

	void SetFunction(QStringList(FileRunnable::*_pf)(QString, QString)) {
		FileRunnable::pf = _pf;
	}

protected:
	void run() {
		QStringList sql_list = ThreadReadRangeFile(path_, table_name_);
		emit SendRunableSignal(sql_list);
		return;
	}

signals:
	void SendRunableSignal(QStringList);

public:
	QStringList ThreadReadRangeFile(QString, QString);
	QStringList(FileRunnable::*pf)(QString _path, QString _table_name);
	QString path_, table_name_;
};

// 数据库文件线程 注册线程中处理函数,线程结束给主线程返回信号
class DBRunnable
	: public QObject
	, public QRunnable
{
	Q_OBJECT

public:
	explicit DBRunnable(QObject* parent = nullptr){}
	~DBRunnable(){}

	void SetFuntion(bool(DBRunnable::*_pf)(QStringList)) {
		DBRunnable::pf = _pf;
	}

protected:
	void run() {
		bool flag = THreadWriteRangeTable(sql_list);
		emit SendRunableSignal(flag);
		return;
	}
signals:
	void SendRunableSignal(bool);

public:
	// 写入Range文件到数据库
	bool THreadWriteRangeTable(QStringList);

public:
	bool(DBRunnable::*pf)(QStringList);
	QStringList sql_list;
	QString uid;
};

class ThreadPractice 
	: public QWidget
{
	Q_OBJECT

public:
	ThreadPractice(QWidget *parent = nullptr);
	~ThreadPractice();

private:
	void Init();

	// 将.range文件转为db文件
	void FileRangeConversion(QString _path, QString _table_name);
	// 将.rcs文件转为db文件
	void FileRcseConversion(QString _path, QString _table_name);
	// 将.img文件转为db文件
	void FileImgConversion(QString _path, QString _table_name);

private slots:
	// 读取文件返回数据库语句
	void ThreadReadRangeFileSlot(QStringList);
	// 写入Range文件到数据库
	void ThreadWriteRangeTableSlot(bool);

private:
	// 线程池对象
	QThreadPool *thread_pool_;

};

ThreadPractice.cpp

#include "ThreadPractice.h"
#include <qgridlayout.h>
#include <qmessagebox.h>
#include <qsqldatabase.h>
#include <qsqlquery.h>
#include <qfile.h>
#include <qtextstream.h>
#include <qsqlerror.h>
#include <qdebug.h>
#include <qdir.h>
#include <quuid.h>

QMutex mutex;

ThreadPractice::ThreadPractice(QWidget *parent)
	: QWidget(parent)
{
	Init();
}

ThreadPractice::~ThreadPractice()
{
}

void ThreadPractice::Init()
{
	thread_pool_ = new QThreadPool();
	thread_pool_->setMaxThreadCount(20);	// 线程池最多20个线程

	QFileInfoList InfoList = QDir(QApplication::applicationDirPath() + "/../../ThreadPractice/data/").entryInfoList();	// 获取当前目录下所有文件
	foreach (QFileInfo fileInfo, InfoList)
	{
		if (!fileInfo.isFile())	// 不是文件继续,只用于加速
			continue;
		if (fileInfo.suffix() == "range") {
			FileRangeConversion(fileInfo.filePath(), fileInfo.fileName());
		}
		if (fileInfo.suffix() == "rcs") {
			FileRcseConversion(fileInfo.filePath(), fileInfo.fileName());
		}
		if (fileInfo.suffix() == "img") {
			FileImgConversion(fileInfo.filePath(), fileInfo.fileName());
		}
	}
}

void ThreadPractice::FileRangeConversion(QString _path, QString _table_name)
{
	// 创建读取文件线程
	FileRunnable *runnable = new FileRunnable();
	runnable->path_ = _path;
	runnable->table_name_ = _table_name;
	connect(runnable, &FileRunnable::SendRunableSignal, this, &ThreadPractice::ThreadReadRangeFileSlot);
	runnable->SetFunction(&FileRunnable::ThreadReadRangeFile);
	runnable->setAutoDelete(true);
	thread_pool_->start(runnable);
}

void ThreadPractice::FileRcseConversion(QString _path, QString _table_name)
{
}

void ThreadPractice::FileImgConversion(QString _path, QString _table_name)
{
}

bool DBRunnable::THreadWriteRangeTable(QStringList _sql_list) {
	uid = QUuid::createUuid().toString();
	{
		// 将range转为数据库的表保存
		QSqlDatabase db = QSqlDatabase::addDatabase("QSQLITE", uid);
		db.setDatabaseName(save_db_path);
		if (!db.open()) {
			QMessageBox::information(0, "unable", "can not open database", QMessageBox::Cancel);
			return false;
		}
		QSqlQuery query(db);	// 默认打开
		// 这个while循环为了解决当有多个文件时会产生多个线程连接数据库,此时创建表格会失败,因此等待成功后在进行后续操作。
		// 此处有个疑问:为什么不能多线程同时连接数据库?
		// 连接数据库创建表格失败,说被锁住了,但是我并没有上锁。
		// 2023/2/1 今天看到sqlite数据库仅支持单用户读写操作 
		while (1) {
			if (!query.exec(_sql_list[0])) {// 0为创建表格
				qDebug() << query.lastError() << "right";
				QThread::msleep(1);		// 如果操作失败则等待(不确定是否是query连接数据库时自动上锁)
				continue;
			}
			break;
		}
		db.transaction();
		for (int i = 1; i < _sql_list.size(); ++i) {
			if (!query.exec(_sql_list[i]))
				qDebug() << query.lastError() << "error";
		}
		db.commit();
		query.clear();
		db.close();
	}
	QSqlDatabase::removeDatabase(uid);
	return true;
}

void ThreadPractice::ThreadWriteRangeTableSlot(bool _flag) {
	if (_flag) {
		qDebug() << "文件转换完成!";
	}
	else {
		qDebug() << "文件转换失败!";
	}
}

// 读去range文件
QStringList FileRunnable::ThreadReadRangeFile(QString _path, QString _table_name) {
	QString save_path = QApplication::applicationDirPath() + "/../../ThreadPractice/data/";
	QString table_name_ = _table_name.mid(1, 5);	// 所有range文件存在一个db文件不同表格里,取文件名第20位开始10个字节的字符作为表名

	save_db_path = save_path + "range.db";

	// 打开文件
	QFile file(_path);
	if (!file.open(QIODevice::ReadOnly | QIODevice::Text))
		qDebug() << "open filed!";

	QStringList sql_list;
	sql_list.clear();
	// 创建表
	QString create_str = QString("create table [%1] ([first] double,[second] double,[third] double,[fourth] double);").arg(table_name_);
	sql_list << create_str;
	// 重复表将之前数据删除
	//QString delete_str = QString("delete from %1;").arg(table_name_);
	//sql_list << delete_str;
	QTextStream in(&file);		// QTextStream读取数据
	in.readLine();
	while (!in.atEnd()) {
		QString fileLine = in.readLine();	// 从第二行读取至下一行
		QStringList list;		// 用于存储文件每行信息
		list.clear();
		list << fileLine.split(" ", QString::SkipEmptyParts);
		// 添加数据
		QString insert_str = QString("insert into [%1] ([first],[second],[third],[fourth]) values ('%2','%3','%4','%5')")
			.arg(table_name_).arg(list.at(0).toDouble()).arg(list.at(1).toDouble()).arg(list.at(1).toDouble()).arg(list.at(3).toDouble());
		sql_list << insert_str;
	}
	return sql_list;
}

// 读完后发送信号并启动写数据库线程
void ThreadPractice::ThreadReadRangeFileSlot(QStringList _sql_list) {
	// 执行sql语句写入数据库创建线程
	DBRunnable * runnable = new DBRunnable();
	QObject::connect(runnable, &DBRunnable::SendRunableSignal, this, &ThreadPractice::ThreadWriteRangeTableSlot);
	runnable->sql_list = _sql_list;
	runnable->SetFuntion(&DBRunnable::THreadWriteRangeTable);
	runnable->setAutoDelete(true);
	// 启动线程之前启动该数据库连接
	thread_pool_->start(runnable);
}

main.cpp

#include <QtCore/QCoreApplication>
#include "ThreadPractice.h"

int main(int argc, char *argv[])
{
    QApplication a(argc, argv);

	ThreadPractice w;

    return a.exec();
}

  • 3
    点赞
  • 73
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

奇树谦

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值