C 语言

关注公众号 jb51net

关闭
首页 > 软件编程 > C 语言 > QT 锁

QT中锁的使用

作者:艾莉丝努力练剑

本文主要介绍了QT中锁的使用,用通俗例子讲透QMutex、QMutexLocker和QReadWriteLock,教你用RAII守卫避免锁泄漏,轻松解决num++不安全问题,提升并发性能

1 ~> Qt 锁机制与线程安全

1.1 线程安全基础

1.1.1 线程安全问题产生根源

1.2 QMutex 互斥锁基础使用

1.2.2 核心接口

1.2.3 复现线程不安全 Demo

业务场景: 两个 QThread 子线程,同时对同一个静态共享变量循环累加。 预期结果:每个线程循环 50000 次,最终结果 100000;不加锁会出现小于 100000 的错误结果。

thread.h

#ifndef THREAD_H
#define THREAD_H

#include <QThread>

class Thread : public QThread
{
    Q_OBJECT
public:
    Thread();
    // 共享静态变量,多线程访问
    static int num;
protected:
    // 线程入口函数,重写run
    void run() override;
};

#endif // THREAD_H

thread.cpp

#include "thread.h"

// 静态成员必须在类外初始化
int Thread::num = 0;

Thread::Thread()
{

}

void Thread::run()
{
    // 不加锁,并发修改共享变量,线程不安全
    for(int i = 0; i < 50000; ++i)
    {
        num++;
    }
}

widget.cpp

#include "widget.h"
#include "ui_widget.h"
#include "thread.h"
#include <QDebug>

Widget::Widget(QWidget *parent)
    : QWidget(parent)
    , ui(new Ui::Widget)
{
    ui->setupUi(this);

    Thread t1;
    Thread t2;
    t1.start();
    t2.start();

    // 主线程等待两个子线程执行完毕,再打印结果;不调用wait会直接打印未完成值
    t1.wait();
    t2.wait();

    qDebug() << Thread::num;
}

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

现象: 输出结果小于 100000,复现数据竞争线程安全 bug。

1.2.4 使用 QMutex 解决线程安全

注意: 多线程互斥,所有线程必须操作同一个锁对象;若每个线程持有独立锁对象,无法实现互斥保护。锁对象定义为static保证全局唯一。

thread.h 修改后

#ifndef THREAD_H
#define THREAD_H

#include <QThread>
#include <QMutex>

class Thread : public QThread
{
    Q_OBJECT
public:
    Thread();
    static int num;
    // 静态互斥锁对象,所有线程共用同一把锁
    static QMutex mutex;
protected:
    void run() override;
};

#endif // THREAD_H

thread.cpp 修改后

#include "thread.h"

int Thread::num = 0;
// 静态锁对象类外定义初始化
QMutex Thread::mutex;

Thread::Thread()
{

}

void Thread::run()
{
    for(int i = 0; i < 50000; ++i)
    {
        mutex.lock();       // 加锁
        num++;              // 临界区,访问共享资源
        mutex.unlock();     // 解锁
    }
}

运行结果输出固定100000,线程安全问题消除。

1.3 锁的异常风险与 RAII 守卫锁 QMutexLocker

1.3.1 手动 lock/unlock 存在的缺陷

手动配对lock() / unlock()极易出现锁泄漏(锁没有释放),场景:

  1. 临界区内部存在if分支return提前退出函数,unlock()不会执行。
  2. 临界区代码抛出异常,程序流程跳转,unlock()无法执行。

锁泄漏后果:其他线程永久阻塞等待锁,发生程序卡死。

1.3.2 RAII 思想

C++ RAII:资源获取即初始化,利用局部对象的生命周期自动管理资源。

1.3.3 QMutexLocker 代码示例

#include "thread.h"
#include <QMutexLocker>

int Thread::num = 0;
QMutex Thread::mutex;

Thread::Thread()
{

}

void Thread::run()
{
    for(int i = 0; i < 50000; ++i)
    {
        // 局部栈对象,构造时lock(&mutex)
        QMutexLocker locker(&mutex);
        num++;
        // 无需手动unlock;出循环迭代的大括号,locker局部对象析构,自动调用unlock
    }
}

1.3.4 Qt 锁与 C++ 标准锁混用说明

1.4 Qt 读写锁 QReadWriteLock

1.4.1 适用场景

少写多读业务场景:大量线程并发读取共享数据,极少线程修改数据。

1.4.2 配套守卫类

1.4.3 读写锁代码示例

#include <QReadWriteLock>

QReadWriteLock rwLock;

// 读操作:多线程可并发执行
{
    QReadLocker locker(&rwLock);
    // 执行只读访问共享资源
} // 作用域结束自动释放读锁

// 写操作:独占访问
{
    QWriteLocker locker(&rwLock);
    // 修改共享资源
} // 作用域结束自动释放写锁

1.5 核心面试考点总结

  1. 使用 QMutex 必须保证多线程操作同一个锁实例,static 成员是常用实现方式。
  2. wait()作用:主线程阻塞,等待子线程执行完毕;不 wait 直接读取共享变量会读到未计算完成的中间值。
  3. num++不是原子操作,是线程不安全典型案例。
  4. 优先使用 RAII 守卫类QMutexLocker,不要裸写 lock/unlock,规避锁泄漏。
  5. QReadWriteLock 优势:少读多写场景提升并发吞吐量;写的时候所有读全部阻塞。
  6. 锁泄漏:锁加完之后没有释放,会造成其他线程永久阻塞。
  7. Qt 锁与 C++ 标准锁底层都是操作系统 mutex 封装,但工程禁止随意混用。

结尾

到此这篇关于QT中锁的使用的文章就介绍到这了,更多相关QT 锁内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!

您可能感兴趣的文章:
阅读全文