QT/C++ 多线程并发下载实践

在python线程池测试例子中,用到了queue的功能,python中,queue是阻塞式获取元素,所以是线程安全的,参考如下的示例:

python 复制代码
from concurrent.futures import ThreadPoolExecutor
from queue import Queue
import time

def worker(queue,id):
    while True:
        item = queue.get()
        if item is None:
            break
        print(f" thread {id} Processing {item}")
        # 模拟处理耗时
        time.sleep(0.5)
    print("Worker exiting")

q = Queue()
executor = ThreadPoolExecutor(max_workers=5)

future_list = []
for i in range(5):
    future = executor.submit(worker, q,i)
    future_list.append(future)

# 提交任务
#future = executor.submit(worker, q)
#future = executor.submit(worker, q)

# 生产数据
for i in range(15):
    q.put(i)
for i in range(15):
    q.put(None)  # 发送终止信号

for f in future_list:
    f.result()

executor.shutdown()

在Qt中,QQueue对象并不是线程安全的,所以我们需要通过改造来实现类似的功能。

一、Qt BlockingQueue定义

为了实现多线性并发,我们需要在队列的take方法中启用QMutexLocker来保证线程安全,并启动QWaitCondition的wait方法以及waitone方法来实现元素阻塞式的访问。

定义"blockingQueue.h":

cpp 复制代码
#include <QWaitCondition>
#include <QQueue>
#include <QMutex>

template <typename T>
class blockingQueue
{
public:
    blockingQueue() {}
    void put(const T& value)
    {
        QMutexLocker locker(&m_mutex);
        m_queue.enqueue(value);
        m_condition.wakeOne();   //唤醒等待队列中的一个线程(来自wait)
    }
    T take()
    {
        QMutexLocker locker(&m_mutex);
        //队列为空,则等待,否则直接返回队列头元素
        while (m_queue.isEmpty()) {
            m_condition.wait(&m_mutex);
        }
        return m_queue.dequeue();
    }
    bool isEmpty() const
    {
        QMutexLocker locker(&m_mutex);
        return m_queue.isEmpty();
    }
    int size() const
    {
        QMutexLocker locker(&m_mutex);
        return m_queue.size();
    }

private:
    QQueue<T> m_queue;
    mutable QMutex m_mutex;
    QWaitCondition m_condition;
};

二、下载任务类定义

该类中,定义下载项目和下载任务对象。并在后续的线程池中,作为参数传递给执行函数。

定义"downloadTask.h":

cpp 复制代码
//下载项目
struct downloadItem
{
	QString src;
	QString dest;
	bool quit;
	downloadItem(bool isQuit = false)
	{
		quit = isQuit;
	}
};

//整个下载任务,包含所有下载项目队列
class downloadTask  : public QObject
{
	Q_OBJECT

public:
	downloadTask(QObject *parent = nullptr);
	~downloadTask();
	bool isQuit() { QMutexLocker locker(&m_mutex);return m_quit; };
	void quit() { QMutexLocker locker(&m_mutex); m_quit = true; };
	downloadItem get() { return m_Queue.take(); };
	void add(downloadItem &item) {  m_Queue.put(item); };;
private:
	mutable QMutex m_mutex;
	bool m_quit = false;
	blockingQueue<downloadItem> m_Queue;
};

三、创建下载任务

以下代码模拟重建100个下载任务,并在正常任务后面添加终止标记

cpp 复制代码
void createTask()
{
    downloadTask* task = new downloadTask();
    //创建任务
    for (size_t i = 0; i < 100; i++)
    {
        downloadItem item;
        item.src = QString("%1.jpg").arg(i);
        item.dest = QString("%1-d.jpg").arg(i);
        task->add(item);
    }
    //终止标记(在需要正常结束的时候添加)
    for (size_t i = 0; i < gc_threadCount; i++)
    {
        downloadItem item(true);
        task->add(item);
    }
}

四、执行下载任务

定义下载任务由5个线程并发执行:

cpp 复制代码
static int gc_threadCount = 5;

下载具体执行将有QRunable类操作:

cpp 复制代码
class taskRunable : public QRunnable
{
public:
	taskRunable(downloadTask* task) :m_task(task) {}

	void run() override 
	{
		while (true)
		{
			//任务中途终止
			if (m_task->isQuit())
				break;
			downloadItem item = m_task->get();
			//任务完成后终止
			if (item.quit)
				break;
			//TODO:下载函数处理item
		}
	}

private:
	downloadTask* m_task;
};

下载任务将有线程池执行。

cpp 复制代码
void startDownload(downloadTask* task)
{
    QThreadPool::globalInstance()->setMaxThreadCount(gc_threadCount);
    for (int i = 0; i < gc_threadCount; ++i)
    {
        auto runable = new taskRunable(task);
        QThreadPool::globalInstance()->start(runable);
    }
    QThreadPool::globalInstance()->waitForDone();
    return;
}

如需要中途结束下载任务,则执行task->quit();

五、总结

至此,我们通过改造Qt的队列,实现了阻塞式的元素获取。并通过线城池的方式,实现了并发下载的示例。

相关推荐
yinhezhanshen几秒前
理解rust里面的copy和clone
开发语言·后端·rust
Zhichao_97几秒前
【UE5 C++课程系列笔记】33——商业化Json读写
c++·ue5
Jtti16 分钟前
PHP在Debian环境上的并发处理能力如何
开发语言·debian·php
时光追逐者20 分钟前
在 Blazor 中使用 Chart.js 快速创建数据可视化图表
开发语言·javascript·信息可视化·c#·.net·blazor
独好紫罗兰23 分钟前
洛谷题单3-P5718 【深基4.例2】找最小值-python-流程图重构
开发语言·python·算法
小天努力学java25 分钟前
【面试题】如何用两个线程轮流输出0-200的值
java·开发语言
云边有个稻草人28 分钟前
【C++】第八节—string类(上)——详解+代码示例
开发语言·c++·迭代器·string类·语法糖auto和范围for·string类的常用接口·operator[]
夏天想1 小时前
vant4+vue3上传一个pdf文件并实现pdf的预览。使用插件pdf.js
开发语言·javascript·pdf·vant
惊鸿一博1 小时前
c++ &&(通用引用)和&(左值引用)区别
开发语言·c++
企鹅不耐热.1 小时前
Scala基础知识6
开发语言·后端·scala