【Qt】Qt系统 | Qt多线程

QThread 多线程

Qt中,多线程处理一般通过 QThread类 实现
QThread 用于在多线程程序中处理并发任务,也可以和进程宏的其他线程共享数据

常用API

QThread 的API和 C++ 并不同,不是使用回调的方式,而是使用多态,仿照 Java 接口设计

API说明
run()线程的入口函数
start()通过调用 run() 开始执行线程。操作系统将根据优先级参数调度线程,如果线程已经在运行,这个函数什么也不做
currentThread()返回一个指向管理当前执行线程的 QThread 的指针
isRunning()如果线程正在运行则返回 true;否则返回 false
sleep() / msleep() / usleep()使线程修改,单位为 秒 / 毫秒 / 微秒
wait()阻塞线程,直到 与此 QThread 对象关联的线程已经完成执行(即从 run() 返回)。如果线程已经完成,这个函数返回 true。如果线程尚未启动,也返回true
terminate()终止线程的执行。线程可以立即终止,也可以不立即终止,这取决于操作系统的调用策略。在 terminate() 之后使用 QThread::wait() 来确保
finished当线程结束时会发出该信号,可以通过信号槽机制实现线程的清理工作

代码示例:新建线程获取实时时间,返回给主线程显示

  1. 设计 UI 界面在这里插入图片描述
  2. 新建 Thread类,继承自 QThread在这里插入图片描述
  3. 重写 run() 函数,声明信号

thread.h

class Thread : public QThread
{
    Q_OBJECT
public:
    Thread();
//重写线程入口函数
    void run() override;
//自定义信号
signals:
    void sendTime(QString time);
};

thread.cpp

void Thread::run()
{
    while(1)
    {
        QString time = QDateTime::currentDateTime().toString("hh:mm:ss");//获取当前时间
        emit Thread::sendTime(time);//发出信号
        sleep(1);
    }
}
  1. 编写按钮槽函数,声明线程对象
    widget.h
class Widget : public QWidget
{
    Q_OBJECT

public:
    Widget(QWidget *parent = nullptr);
    ~Widget();

//按钮的槽函数
private slots:
    void on_pushButton_clicked();

private:
    Ui::Widget *ui;
    Thread thread;//线程对象

};

widget.cpp

Widget::Widget(QWidget *parent)
    : QWidget(parent)
    , ui(new Ui::Widget)
{
    ui->setupUi(this);
    //捕获线程发出的信号
    connect(&thread, &Thread::sendTime, this, [=](QString time){
        ui->label->setText(time);
    });
}

Widget::~Widget()
{
    delete ui;
}


void Widget::on_pushButton_clicked()
{
    thread.start();//开启线程
}

运行结果如下:
在这里插入图片描述


多线程 connect 参数补充

  1. Qt规定 UI 界面只能由主线程改变,线程函数内部不允许操作 UI 图形界面,一般用作数据处理
  2. connect() 函数第五个参数表示的为连接的方式,且只有在多线程的时候才有意义

connect() 函数原型如下:
在这里插入图片描述
第五个参数为Qt::ConnectionType,用于指定信号和槽的连接类型。同时影响信号的传递方式和槽函数的执行顺序。Qt::ConnectionType 提供方式如下:

方式说明
Qt::AutoConnection在 Qt 中,会根据信号和槽函数所在的线程自动选择连接类型。如果信号和槽函数在同一线程中,那么使用Qt::DirectConnection 类型;如果它们位于不同的线程中,那么使用 Qt::QueuedConnection 类型
Qt::DirectConnection当信号发出时,槽函数会立即在同一线程中执行。这种连接类型适用于信号和槽函数在同一线程中的情况,可以实现直接的函数调用,但需要注意线程安全
Qt::QueuedConnection当信号发出时,槽函数会被插入到接收对象所属的线程的事件队列中,等待下一次事件循环时执行。这种连接类型适用于信号和槽函数在不同线程中的情况,可以确保线程安全
Qt::BlockingQueuedConnection与 Qt::QueuedConnection 类似,但是发出信号的线程会被阻塞,直到槽函数执行完毕,这种连接类型适用于需要等待槽函数执行完毕再继续的场景,需要注意可能引起线程死锁
Qt::UniqueConnection这是一个标志,可以使用 位或 与上述任何一种连接类型组合使用

线程安全

实现线程互斥和同步常用的类有:

  • 互斥锁:QMutex,QMutexLocker
  • 条件变量:QWaitCondition
  • 信号量:QSemaphore
  • 读写锁:QReadLocker、QWriteLocker、QReadWriteLock

互斥锁

互斥锁是一种保护和防止多个线程同时访问同一对象实例的方法,在 Qt 中,互斥锁主要是通过 QMutex类 实现

  • QMutex

特点:QMutex 是 Qt 框架提供的互斥锁类,用于保护共享资源的访问,实现线程间的互斥操作。
用途:在多线程环境下,通过互斥锁来控制对共享数据的访问,确保线程安全。


QMutex mutex;
mutex.lock();//加锁

//临界区访问共享资源
//......

mutex.unlock();//解锁
  • QMutexLocker

特点:QMutexLocker 是 QMutex 的辅助类,也可以对互斥锁进行上锁和解锁操作。使用RAII 机制,防止因抛异常等操作,执行流跳转,互斥锁未解锁,导致其他线程死锁。原理是让互斥锁的上锁和解锁随对象的构造和析构
用途:简化对互斥锁的上锁和解锁操作,避免忘记解锁导致的死锁等问题

QMutex mutex;
{
	//locker的生命周期随作用域
	QMutexLocker locker(&mutex);//加锁随对象构造

	//访问共享资源
	//...
}//出作用域,locker析构解锁

  • 读写锁

特点:
QReadWriteLock 是读写锁,用于控制读和写的并发访问
QReadLocker 用于读操作上锁,允许多个线程同时读取共享资源
QWriteLocker 用于写操作上锁,只允许一个线程写入共享资源
用途:在某些情况下,多个线程可以同时读取共享数据,但只有一个线程能够进行写操作。读写锁提供了更高效的并发访问方式:

QReadWriteLock rwlock;
//在读操作中使用读锁
{
	QReadLocker locker(&rwlock);//上读锁
	
	//读写共享资源
	//...
}//作用域结束自动解锁

//在写操作中使用写锁
{
	QWriteLocker locker(&rwlock);//上写锁
	
	//修改共享资源
	//...
}//作用域结束自动解锁

代码示例:多个线程对同一变量进行累加

  1. 创建 QWidget 项目,新建 Thread类,继承自 QThread

在这里插入图片描述

  1. 编写 thread.h,声明 run() 函数 和 成员变量
class Thread : public QThread
{
    Q_OBJECT
public:
    Thread(QObject *parent = nullptr);
    //线程启动函数
    void run();
public:
    static QMutex mutex;//多线程互斥锁
    static int num;//多线程累加的变量
};

编写 thread.cpp,此处不加互斥锁

//静态成员变量类外初始化
int Thread::num = 0;
QMutex Thread::mutex;

Thread::Thread(QObject *parent)
    :QThread(parent)
{}

void Thread::run()
{
    for(int i = 0; i < 50000; ++i)
        ++this->num;
}
  1. 编写 widget.cpp
Widget::Widget(QWidget *parent)
    : QWidget(parent)
    , ui(new Ui::Widget)
{
    ui->setupUi(this);
    //创建线程
    Thread t1(this), t2(this);
    //启动线程
    t1.start();
    t2.start();
    //回收线程资源
    t1.wait();
    t2.wait();

    qDebug() << Thread::num;
}

运行结果如下:
在这里插入图片描述
可以看到,两个线程都各自对 num 累加50000次,预期结果应该是 100000,但是因为多线程没有互斥访问共享变量,导致错误

  1. 多线程访问共享资源,加锁。编写thread.cpp
void Thread::run()
{
    for(int i = 0; i < 50000; ++i)
    {
        QMutexLocker locker(&this->mutex);
        ++this->num;
    }
}

运行结果如下:
在这里插入图片描述

条件变量

条件变量是多线程编程中的一种同步机制,用于协调线程之间的执行顺序或状态变化。它允许线程在某些条件满足之前阻塞执行,并在条件满足后被通知并恢复执行。条件变量通常与互斥锁配合使用,以确保在访问共享资源时的线程安全。
Qt 中,QWaitCondition 实现上述机制
特点:QWaitCondition 是 Qt 框架提供的条件变量类,用于线程之间的消息通信和同步
用途:在某个条件满足时等待或唤醒线程,用于线程的同步和协调

QMutex mutex;
QWaitCondition cond;
//加锁
mutex.lock();
//检查条件是否满足,若不满足则等待
while(!....)//循环判断是否满足条件
{
	//不满足则等待条件满足,并释放锁
	cond.wait(&mutex);
}
//到此是条件满足
//...执行逻辑

//唤醒其他在该条件变量上等待的线程,再解锁
cond.wakeAll();
mutex.unlock();

信号量

信号量是一种用于进程或线程间同步和互斥的机制。它可以控制对共享资源的访问,防止出现竞争条件。信号量主要分为两种类型:计数信号量和二值信号量(或互斥量)。计数信号量可以允许多个线程访问共享资源,而二值信号量则仅允许一个线程访问。
信号量类似增强的互斥锁,不仅能完成上锁和解锁操作,而且可以跟踪可用资源的数量

特点:QSemaphore 是 Qt 框架提供的计数信号量,用于控制同时访问共享资源的线程数量
用途:限制并发线程数量,用于解决一些资源有限的问题

QSemaphore sem(2);//同时允许两个线程访问共享资源
//在需要访问共享资源的线程中
sem.acquire();//尝试获取信号量,若满则阻塞

//访问共享资源
//...

sem.release();//释放信号量

结束语
感谢你的阅读
如果觉得本篇文章对你有所帮助的话,不妨点个赞支持一下博主,拜托啦,这对我真的很重要。
在这里插入图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值