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的队列,实现了阻塞式的元素获取。并通过线城池的方式,实现了并发下载的示例。

相关推荐
Yu_Lijing3 分钟前
MySQL进阶学习与初阶复习第二天
数据库·c++·学习·mysql
超浪的晨31 分钟前
Java 代理机制详解:从静态代理到动态代理,彻底掌握代理模式的原理与实战
java·开发语言·后端·学习·代理模式·个人开发
l1t38 分钟前
开源嵌入式数组引擎TileDB的简单使用
c语言·数据库·c++
咖啡の猫41 分钟前
bash的特性-bash中的引号
开发语言·chrome·bash
java叶新东老师1 小时前
idea提交时忽略.class、.iml文件和文件夹或目录的方法
java·开发语言
走过,莫回头1 小时前
在OpenMP中,#pragma omp的使用
开发语言·openmp
Warren981 小时前
Java Collections工具类
java·开发语言·笔记·python·学习·oracle·硬件工程
工程师0072 小时前
C#多线程,同步与异步详解
开发语言·c#·多线程·同步·异步编程
xzkyd outpaper2 小时前
Kotlin中Flow
android·开发语言·kotlin
“αβ”2 小时前
线程安全的单例模式
linux·服务器·开发语言·c++·单例模式·操作系统·vim