【Qt开发】多线程QThread(通过QObject::moveToThread)和QMutex互斥锁的配置和基本函数
多线程
Qt官方给了两种方法连运行多线程函数
一种是直接用QThread的run()方法
还有一种就是继承QObject 用moveToThread方法去放到QThread里执行
在官方文档中 推荐使用后者 前者是Qt4之前的版本用的
QThread
使用QThread需要自己定义一个继承QThread的类(最简单)
比如QThread的写法一般是:
class WorkerThread : public QThread
{
/* 用到信号槽即需要此宏定义 */
Q_OBJECT
public:
/* 重写run方法,继承QThread的类,只有run方法是在新的线程里 */
void run() override
{
QString result = "线程开启成功";
qDebug()<<result<<endl;
}
在这里需要加上override
关键字重写run函数(在定义时 加不加都可以 但推荐加 具体看编译器标准)
调用时 则直接用start方法即可以多线程方式运行run函数
if (!workerThread->isRunning())
{
workerThread->start();
}
另外 可以用信号和槽函数来发送线程执行完成的信号或结果
signals:
/* 声明一个信号,译结果准确好的信号 */
void resultReady(const QString &s);
};
重写的run函数加上发送信号 最后这个result会在主线程中打印
void run() override
{
QString result = "线程开启成功";
emit resultReady(result);
}
在大类下用信号槽连接和槽函数
connect(workerThread, SIGNAL(resultReady(QString)),this, SLOT(handleResults(QString)));
void MainWindow::handleResults(const QString &result)
{
/* 打印出线程发送过来的结果 */
qDebug()<<result<<endl;
}
最简单的QThread示例:
#ifndef WORKER_H
#define WORKER_H
#include <QThread>
class Worker : public QThread
{
public:
Worker();
void run();
void printFunc();
};
#endif // WORKER_H
主函数调用:
#include <iostream>
#include <QDebug>
#include "Worker.h"
using namespace std;
int main()
{
Worker w;
w.start();
qDebug()<<"主线程ThreadID: "<<QThread::currentThreadId();
w.printFunc();
while (1)
{
}
return 0;
}
w.start();
调用后就是用多线程运行run函数内容
而成员函数w.printFunc();
则继续在主线程中执行
线程关闭则用如下语句:
this->workerThread->quit();
if(workerThread->wait())
{
}
先quit再用wait等待 wait可以传参 表示延时多长时间
可以将关闭函数放在析构函数中执行
QObject::moveToThread
采用这个方法 需要继承QObject类
然后将其使用moveToThread方法移到一个线程里面执行
比如继承QObject类的Worker类
下面有一系列成员函数work1 work2等
moveToThread方法需要传入一个QThread地址来调用
表明把任务移动到该地址的QThread执行
这里可以把不同的工作函数传入同一个QThread执行(不同时间调用不同的工作)
也可以把同一个工作函数传入多个QThread执行(需要定义多个QThread)
对于管理多个线程 比较方便 但对于单一的线程 直接用QThread常规方法即可
通过主线程中的一个槽函数来连接
connect(this, SIGNAL(startWork(QString)), Worker_1, SLOT(Judg_doWork(QString)));
其中 startWork是信号
signals:
void startWork(const QString &);
同样调用时 需要通过start方法先开启移动后的QThread
然后发送槽函数信号
bool startThread(void)
{
if(!workerThread->isRunning())
{
workerThread->start();
return true;
}
return false;
}
Worker_1->startThread();
emit this->startWork("starWork\n");
startWork信号发送后 即跳转到Judg_doWork(QString)
中执行
然后我们写个判断就可以进行传参了
其中 Judg_doWork也要声明为槽函数
public slots:
void Judg_doWork(const QString ¶meter)
{
this->isCanRun=true;
//执行
doWork(parameter);
}
public:
void doWork(const QString ¶meter)
{
if(parameter=="")
{
QMutexLocker locker(&this->lock);
return;
}
else
{
while(this->isCanRun)
{
QMutexLocker locker(&this->lock);
QThread::msleep(200);
qDebug()<<"开启线程"<<QThread::currentThreadId()<<"\n";
}
return;
}
}
关闭函数一样 但要加一个
可以将关闭函数放在析构函数中执行
另外 还需要将线程本身的finished()
信号连接到deleteLater()在这里插入代码片
函数
connect(workerThread, SIGNAL(finished()),this, SLOT(deleteLater()));
connect(workerThread, SIGNAL(finished()), workerThread, SLOT(deleteLater()));
我自己写的关于QObject的类如下:
class MY_Thread_Worker;
class MY_Thread_Worker : public QObject
{
Q_OBJECT
private:
QMutex lock;
bool isCanRun;
public slots:
void Judg_doWork(const QString ¶meter)
{
this->isCanRun=true;
//执行
doWork(parameter);
}
public:
QThread *workerThread;
void doWork(const QString ¶meter)
{
if(parameter=="")
{
QMutexLocker locker(&this->lock);
return;
}
else
{
while(this->isCanRun)
{
QMutexLocker locker(&this->lock);
QThread::msleep(200);
qDebug()<<"开启线程"<<QThread::currentThreadId()<<"\n";
}
return;
}
}
void stopWork(void)
{
QMutexLocker locker(&this->lock);
this->isCanRun = false;
}
bool startThread(void)
{
if(!workerThread->isRunning())
{
workerThread->start();
return true;
}
return false;
}
bool stopThread(void)
{
if(workerThread->isRunning())
{
stopWork();
return true;
}
return false;
}
void closeThread(void)
{
stopWork();
this->workerThread->quit();
if(workerThread->wait())
{
}
}
MY_Thread_Worker(QThread * worker_Thread = nullptr)
{
if(worker_Thread==nullptr)
{
this->workerThread = new QThread;
}
else
{
this->workerThread = worker_Thread;
}
this->moveToThread(workerThread);
this->stopWork();
connect(workerThread, SIGNAL(finished()),this, SLOT(deleteLater()));
connect(workerThread, SIGNAL(finished()), workerThread, SLOT(deleteLater()));
// connect(this, SIGNAL(startWork(QString)), worker, SLOT(Judg_doWork(QString))); 主窗口发送函数
// connect(worker, SIGNAL(resultReady(QString)),this, SLOT(handleResults(QString))); 主窗口接收函数
}
~MY_Thread_Worker()
{
closeThread();
}
};
#endif // MY_QT_DEF_H
调用方式:
Worker_1 = new MY_Thread_Worker(new QThread);
connect(this, SIGNAL(startWork(QString)), Worker_1, SLOT(Judg_doWork(QString)));
qDebug()<<"主线程"<<QThread::currentThreadId()<<"\n";
Worker_1->startThread();
emit this->startWork("starWork\n");
其中 isCanRun
变量是我定义用来判断线程状态的 本质上也可以作为一个软件锁来使用 但是我也加了QMutex锁
多线程测试和QThread::currentThreadId()
用QThread::currentThreadId()
可以查看当前程序的线程地址(主线程也可以用)
进入多线程前 打印一次主线程
然后我开了两个定时器分别0.5s和1s 子线程则200ms打印一次
可以看到 定时器都属于主线程里面的
而子线程则不一样
QMutex互斥锁和线程同步
为了避免多次调用(除非你想)或者多线程访问共享资源时打架(除非你想) 则需要引入线程锁
线程锁的作用就是在运行时上锁 运行后释放 如果运行时检测到锁了 则不执行 直到锁被释放后继续执行
通过声明QMutex变量即可使用lock方法加锁
可以在不同的线程中使用 以达到同步的作用(需要全局变量)
也可以在一个线程中使用防止被多次调用
比如:
static QMutex MessageOutput_Mutex;
MessageOutput_Mutex.lock();
结束后解锁则调用
MessageOutput_Mutex.unlock();
比如例子:
#include <QCoreApplication>
#include <QThread>
#include <QDebug>
#include <QMutex>
// 定义共享资源
int sharedValue = 0;
QMutex mutex;
// 定义一个线程类
class MyThread : public QThread
{
public:
void run() override {
for(int i = 0; i < 5; i++) {
mutex.lock(); // 加锁
sharedValue++; // 访问共享资源
qDebug() << "Thread ID: " << QThread::currentThreadId() << " - Shared Value: " << sharedValue;
msleep(1000); // 线程休眠1秒
mutex.unlock(); // 解锁
}
}
};
int main(int argc, char *argv[])
{
QCoreApplication a(argc, argv);
MyThread thread1;
MyThread thread2;
thread1.start();
thread2.start();
thread1.wait();
thread2.wait();
qDebug() << "Final Shared Value: " << sharedValue;
return a.exec();
}
执行效果:
如果不加锁则会打架
另外 QMutexLocker提供了一种更加便捷的方式
通过传入QMutex变量来进行上锁
QMutexLocker locker(&this->lock);
而在程序退出后会自动解锁
此方法可以在循环里面调用 每次循环开始时调用即可
附录:C语言到C++的入门知识点(主要适用于C语言精通到Qt的C++开发入门)
C语言与C++的不同
C语言是一门主要是面向工程的语言
C++则是面向对象
C语言中 某些功能实现起来较为繁琐
比如结构体定义:
一般写作:
typedef struct stu_A
{
}A;
也可以写作:
typedef struct
{
}A;
但 大括号后面的名称是不可省去的
不过 C++的写法就比较简单
除了支持上述写法外
也支持直接声明
typedef struct A
{
}
另外 C++是完全支持C语言库和语法的
不过C++里面的库也有些很方便的高级功能用法 只不过实现起来可能不如C的速度快
再者 C语言与C++的编译流程不一样
C语言没有函数重载 所以给编译器传参就是直接传函数名称
但是C++除了传函数名称外 还会穿函数的参数、类型等等 以实现函数重载
C++中写C语言代码
上文提到 C++可以完全兼容C的写法
但是编译流程也还是不一样
所以如果在编译层面进行C语言代码编译 则通常用以下方法:
extern "C"
{
...
}
表面大括号内的内容用C的方法进行编译
另外 如果还是用C++的编译器 但要实现C语言函数 则需要用到C语言的库
在C语言中 我们一般用如下方法导入库
#include <stdio.h>
此方法同样适用于C++ 但是C++可以更方便的写成去掉.h的方式
比如:
#include <iostream>
在C++中 为了调用C语言的库 可以采用在原库名称前加一个"c"的方式导入
如:
#include <cstdio>
这样就可以使用printf等函数了 甚至比C++的std方法更快
C语言到C++的知识点
Qt开发中需要了解的C++基础知识
namespace
C++面向对象的特性下诞生的一个名称
表示某个函数、变量在某个集合下 用作namespace
比如 <iostream>
库中的关键字cin在std下 则写作std::cin
std就是namespace
::表示某空间下的某某
前面是空间名称 后面是变量、函数名称
用using namespace
可以告诉编译器以下都用xx名称空间
比如:
using namespace std;
cout<<"a";
如果没有告诉编译器所使用的空间名称 则要写成:
std::cout<<"a";
同样 可以自定义某一段代码属于哪个空间:
namespace xx
{
...
}
输入输出
在C++中 用iostream作为输入输出流的库
#include <iostream>
用cin和cout关键字进行输入和输出
如:
using namespace std;
int a=0;
cin>>a; //输入到a
cout<<a; //输出a
类比scanf和printf
同样 还有一个关键字endl表示换行
cout和cin的传参是不固定的
由编译器自行裁定
字符串类型
在C语言中 常用char *表示字符串
但是在C++中 可以直接用string类型
比如:
char * s="456";
string str="123";
由于cout的特性 这两种字符串都可以直接打印
但如果使用C语言中printf的打印方式时 采用%s方式打印字符串 则不能传入string类型
class类
C++的核心就是class
同Python等支持面向对象的语言一样
可以理解成一个支持函数、继承、自动初始化、销毁的结构体
在class类中 有private
私有、public
公有变量
前者只能内部访问 后者可以外部调用使用
如:
class A
{
public:
int a;
private:
int b;
}
a可以用A.a的方式方位 b则外部无法访问
构造函数和析构函数(解析函数)
构造函数可以理解成对类的初始化 反之析构函数则是退出时进行销毁前的函数
两者需要与类的名称相同 析构函数则在前面加一个~表示非
如:
class A
{
public:
int a;
A();
~A();
private:
int b;
}
A::A()
{
...
}
A::~A()
{
...
}
构造函数可以定义传参 析构函数则不行
类的继承
如果有两个类A和B 想让A里面包含B 则可以写作继承的写法
继承后 A类的变量可以直接调用B下面的成员
如:
class B
{
int b;
}
class A: public B
{
int a;
}
在定义A后 可以访问到B的成员b 当然 继承也可以私有