【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 当然 继承也可以私有












![[论文笔记]ZeRO: Memory Optimizations Toward Training Trillion Parameter Models](https://img-blog.csdnimg.cn/img_convert/fe8c5d3ec56640d819ac0cf735b83f6a.png)






