您的位置:首页 > 编程语言 > Qt开发

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
#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,下次介绍。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: