qt 利用线程池和锁搭建客户端框架(一)
2018-03-18 23:15
190 查看
做客户端开发很久了,一直在尝试搭建一个更好的客户端架构;在看了qt的QQucikAsyicImageProvider这个类的官方示例后发现,qt的QRunable类在run函数中也可以通过发送信号的方式与主线程通信,之前看过的说明都说QRunable不好与主线程交互,这次算看到实例了。访方法是通过多继承的方式继承QObject和QRunable,这个代码编写的时候要注意,多继承时QObject要写在前面,这个我就不知道了。
通信多继承之后就可以在run函数中与主线程通信,但是主线程怎么与run中的子线程通信呢;从原始的角度看就只有启动线程是那一个,run函数执行结束就完了,因为它是没有消息循环的。也就是在没有死循环的情况下,它只能执行完了就退出线程了。由于我是要保证主线程随时能与它通信,所以第一时间就想到了死循环;而后主线程与子线程的通信,我选择了锁和条件表达式,这样就能阻塞等待了。代码如下
#ifndef WORKERAPI_H
#define WORKERAPI_H
#include <QRunnable>
class QVariant;
namespace BLL {
enum WorkerType{
RunForever,
RunOnce
};
class Worker : public QRunnable
{
public:
Worker() = default;
virtual void quit() = 0;
virtual WorkerType workerType() const = 0;
protected:
virtual void pushBackTask(QVariant &data) = 0;
virtual QVariant getTask() = 0;
};
}
#endif // WORKERAPI_H先声明了接口,下面是一种实现#ifndef BASEWORKER_H
#define BASEWORKER_H
#include "workerapi.h"
#include <mutex>
#include <condition_variable>
#include <queue>
#include <QVariant>
QT_FORWARD_DECLARE_CLASS(QVariant)
namespace BLL{
class BaseWorker : public QObject , public Worker
{
Q_OBJECT
public:
explicit BaseWorker(QObject *parent = nullptr);
~BaseWorker();
Q_INVOKABLE void quit() override;
protected:
virtual void pushBackTask(QVariant &data) override;
virtual QVariant getTask() override;
private:
std::mutex _mtx;
std::condition_variable _cv;
std::queue<QVariant> _argVec;
};
}#endif // BASEWORKER_H
下面是一个死循环的最终与业务相关的类了#ifndef RUNFOREVERWORKER_H
#define RUNFOREVERWORKER_H
#include "core/baseworker.h"
#include "DLL/testhttp.h"
#include "DLL/testtcp.h"
#include <QVariant>
namespace BLL {
class RunForeverWorker : public BaseWorker
{
Q_OBJECT
public:
RunForeverWorker(QObject *parent = nullptr):BaseWorker(parent){}
WorkerType workerType() const override;
Q_INVOKABLE void requestData();
signals:
void sigResponse(QVariant result);
protected:
void run() override;
};
}
#endif // RUNFOREVERWORKER_H
在run函数中有一个死循环,每次当队列不为空时都会取得一个数据来执行业务操作,执行完后继续等待下一个数据,如果队列为空就阻塞在那里,当数据处理好后就发送信号到主线程。主线程要执行任务时只需要调用接口pushBack'Task(QVariant&data)就可以将数据放入队列,这时会激活一个子线程来完成该任务。
值得提醒的是该类最好不要指定父对象,因为我的设计中并没有调用setAutoDelete(false),也就是退出run()函数会自动释放,在主线程中大部分是与GUI相关的,这样做会造成异常;另外一点只有在run()函数中的代码才是在子线程中运行的,如果将变量定义为类的成员变量,那么如果使用QThreadPool::start()多次同一个对象的Worker就会有多个run()子线程运行,但是对象只有一个也就是变量只有一个,这样就会造成多个子线程同时共用方线程的变量,造成异常。所以最好将子线程需要定义的东西直接在run()函数中新建,保证每个线程都有一份自已的变量,各自独立。之前有一种设计就是把与锁相关的函数封装成另一外类用传参的方法,传入到业务相关的Worker中调用;但在释放时,由于另一个类在主线程中释放只需调用quit()函数非常快就释放了,但子线程如果正在处理某个业务,当完成时去取下一个无效的数据时就可以退出,但由于需要用另一个类的指针来访问,就会造成异常。
终上,这是最安全的一种设计了。另外加入了Worker的管理类,用来启动和管理Worker,下次介绍。
通信多继承之后就可以在run函数中与主线程通信,但是主线程怎么与run中的子线程通信呢;从原始的角度看就只有启动线程是那一个,run函数执行结束就完了,因为它是没有消息循环的。也就是在没有死循环的情况下,它只能执行完了就退出线程了。由于我是要保证主线程随时能与它通信,所以第一时间就想到了死循环;而后主线程与子线程的通信,我选择了锁和条件表达式,这样就能阻塞等待了。代码如下
#ifndef WORKERAPI_H
#define WORKERAPI_H
#include <QRunnable>
class QVariant;
namespace BLL {
enum WorkerType{
RunForever,
RunOnce
};
class Worker : public QRunnable
{
public:
Worker() = default;
virtual void quit() = 0;
virtual WorkerType workerType() const = 0;
protected:
virtual void pushBackTask(QVariant &data) = 0;
virtual QVariant getTask() = 0;
};
}
#endif // WORKERAPI_H先声明了接口,下面是一种实现#ifndef BASEWORKER_H
#define BASEWORKER_H
#include "workerapi.h"
#include <mutex>
#include <condition_variable>
#include <queue>
#include <QVariant>
QT_FORWARD_DECLARE_CLASS(QVariant)
namespace BLL{
class BaseWorker : public QObject , public Worker
{
Q_OBJECT
public:
explicit BaseWorker(QObject *parent = nullptr);
~BaseWorker();
Q_INVOKABLE void quit() override;
protected:
virtual void pushBackTask(QVariant &data) override;
virtual QVariant getTask() override;
private:
std::mutex _mtx;
std::condition_variable _cv;
std::queue<QVariant> _argVec;
};
}#endif // BASEWORKER_H
#include "baseworker.h" #include <QThread> #include <QDebug> BLL::BaseWorker::BaseWorker(QObject *parent): QObject(parent) { } BLL::BaseWorker::~BaseWorker() { qDebug() << "delete Worker"; } void BLL::BaseWorker::quit() { QVariant invalid; pushBackTask(invalid); } void BLL::BaseWorker::pushBackTask(QVariant &data) { std::lock_guard<std::mutex> lck(_mtx); _argVec.push(data); _cv.notify_one(); } QVariant BLL::BaseWorker::getTask() { std::unique_lock<std::mutex> lck(_mtx); _cv.wait(lck,[this]{return !_argVec.empty();}); QVariant argData = _argVec.front(); _argVec.pop(); lck.unlock(); return argData; }可以看到我在其中重要的两个接口中使用了锁和条件表达式,另外一个quit()接口显示易见就是用来退出的了,往队列里面放一个空的数据,当子线程拿到数据是无效的时候就直接返回run()函数这样子线程就退出了。
下面是一个死循环的最终与业务相关的类了#ifndef RUNFOREVERWORKER_H
#define RUNFOREVERWORKER_H
#include "core/baseworker.h"
#include "DLL/testhttp.h"
#include "DLL/testtcp.h"
#include <QVariant>
namespace BLL {
class RunForeverWorker : public BaseWorker
{
Q_OBJECT
public:
RunForeverWorker(QObject *parent = nullptr):BaseWorker(parent){}
WorkerType workerType() const override;
Q_INVOKABLE void requestData();
signals:
void sigResponse(QVariant result);
protected:
void run() override;
};
}
#endif // RUNFOREVERWORKER_H
#include "runforeverworker.h" #include <QThread> #include <QDebug> BLL::WorkerType BLL::RunForeverWorker::workerType() const { return RunForever; } void BLL::RunForeverWorker::requestData() { QVariant argData("-------------forever-----------------"); pushBackTask(argData); } void BLL::RunForeverWorker::run() { std::shared_ptr<DLL::TestHttp> _http(new DLL::TestHttp); std::shared_ptr<DLL::TestSocket> _tcp(new DLL::TestSocket); while (true) { QVariant arguments = getTask(); if(!arguments.isValid())return; qDebug() << "get a task " << QThread::currentThreadId() << arguments << _tcp.get() << _http.get(); _tcp->search(); CURLcode resCode = CURLcode(_http->searchHistoty()); if(resCode != CURLE_OK){ emit sigResponse(QVariant::fromValue(QVariant::fromValue(QString(curl_easy_strerror(resCode))))); }else { emit sigResponse(QVariant::fromValue(QVariant::fromValue(QString::fromStdString(_http->records())))); } } }可以看到
在run函数中有一个死循环,每次当队列不为空时都会取得一个数据来执行业务操作,执行完后继续等待下一个数据,如果队列为空就阻塞在那里,当数据处理好后就发送信号到主线程。主线程要执行任务时只需要调用接口pushBack'Task(QVariant&data)就可以将数据放入队列,这时会激活一个子线程来完成该任务。
值得提醒的是该类最好不要指定父对象,因为我的设计中并没有调用setAutoDelete(false),也就是退出run()函数会自动释放,在主线程中大部分是与GUI相关的,这样做会造成异常;另外一点只有在run()函数中的代码才是在子线程中运行的,如果将变量定义为类的成员变量,那么如果使用QThreadPool::start()多次同一个对象的Worker就会有多个run()子线程运行,但是对象只有一个也就是变量只有一个,这样就会造成多个子线程同时共用方线程的变量,造成异常。所以最好将子线程需要定义的东西直接在run()函数中新建,保证每个线程都有一份自已的变量,各自独立。之前有一种设计就是把与锁相关的函数封装成另一外类用传参的方法,传入到业务相关的Worker中调用;但在释放时,由于另一个类在主线程中释放只需调用quit()函数非常快就释放了,但子线程如果正在处理某个业务,当完成时去取下一个无效的数据时就可以退出,但由于需要用另一个类的指针来访问,就会造成异常。
终上,这是最安全的一种设计了。另外加入了Worker的管理类,用来启动和管理Worker,下次介绍。
相关文章推荐
- qt 利用线程池和锁搭建客户端框架(二)
- 利用socket搭建一个多客户端/服务器的框架
- C++搭建框架,利用OpenGL、GDAL、Qt进行分块显示遥感影像
- Qt 使用线程池搭建widgets开发框架
- topic1:Qt入门之搭建环境与hello world看Qt开发框架
- 利用Qt Phonon框架制作音视频播放器
- 安卓微博客户端 第二天 系统主框架的搭建
- 局域网LanQQ聊天项目(二)服务器和客户端整体框架的搭建
- 利用Photos 框架搭建美图秀秀相册选择器
- centos 6.5环境利用iscsi搭建SAN网络存储服务及服务端target和客户端initiator配置详解
- JAVA线程池管理及分布式HADOOP调度框架搭建
- 基于C/S架构的3D对战网络游戏C++框架_04客户端详细设计与OpenGL、Qt基础
- 使用QT搭建点云显示框架系列一——QGLViewer
- 【干货】利用MVC5+EF6搭建博客系统(四)(上)前后台页面布局页面实现,介绍使用的UI框架以及JS组件
- [PHP]利用XAMPP搭建本地服务器, 然后利用iOS客户端上传数据到本地服务器中(二.配置MySQL数据库)
- 利用django框架,手把手教你搭建数据可视化系统(一)
- 利用SpringCloud搭建一个最简单的微服务框架
- 利用Qt Phonon框架制作音视频播放器
- 利用ThinkPhp框架搭建网站
- 利用truffle框架部署应用到自己搭建的私有链