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

相关推荐
Python×CATIA工业智造1 小时前
Frida RPC高级应用:动态模拟执行Android so文件实战指南
开发语言·python·pycharm
十五年专注C++开发1 小时前
CMake基础:条件判断详解
c++·跨平台·cmake·自动化编译
我叫小白菜2 小时前
【Java_EE】单例模式、阻塞队列、线程池、定时器
java·开发语言
狐凄2 小时前
Python实例题:基于 Python 的简单聊天机器人
开发语言·python
weixin_446122463 小时前
JAVA内存区域划分
java·开发语言·redis
悦悦子a啊3 小时前
Python之--基本知识
开发语言·前端·python
QuantumStack4 小时前
【C++ 真题】P1104 生日
开发语言·c++·算法
天若有情6734 小时前
01_软件卓越之道:功能性与需求满足
c++·软件工程·软件
whoarethenext4 小时前
使用 C++/OpenCV 和 MFCC 构建双重认证智能门禁系统
开发语言·c++·opencv·mfcc
代码的奴隶(艾伦·耶格尔)5 小时前
后端快捷代码
java·开发语言