Qt开发:多线程编程中资源的同步及并发

一、QMutex 简介

  在 Qt 中,互斥锁(QMutex)用于在多线程编程中保护共享资源,防止多个线程同时访问同一个资源导致数据冲突或程序异常。它的作用是确保同一时刻只有一个线程能访问某段临界代码(通常是共享数据操作)。

1.1 QMutex 使用方法

#include <QMutex>

QMutex mutex;

void exampleFunction() {
   
   
    mutex.lock();           // 加锁
    // 临界区:访问共享资源
    ...
    mutex.unlock();         // 解锁
}

注意:如果函数中途 return 或 抛出异常 而忘记 unlock(),可能导致死锁。

1.2 常用函数介绍

1.2.1 bool QMutex::isRecursive() const
函数说明:
当一个互斥锁是“递归的”时,同一个线程可以多次加锁,而不会造成死锁。例如,一个递归锁可以在递归函数或多个函数层次中被同一个线程多次锁定,只要最终解锁的次数与加锁次数匹配即可。它用于判断当前这个 QMutex 是否是以递归方式创建的。

使用场景举例:

#include <QMutex>
#include <QDebug>

int main() {
   
   
    QMutex mutex(QMutex::Recursive);  // 创建一个递归互斥锁

    if (mutex.isRecursive()) {
   
   
        qDebug() << "This mutex is recursive.";
    } else {
   
   
        qDebug() << "This mutex is not recursive.";
    }

    return 0;
}

输出结果:

This mutex is recursive.

与递归锁相关的背景知识:

explicit QMutex(QMutex::RecursionMode mode = QMutex::NonRecursive);
  • QMutex::Recursive:允许同一个线程多次加锁。
  • QMutex::NonRecursive(默认):不允许同一线程多次加锁,否则会导致死锁。

例:递归函数中使用递归锁

QMutex mutex(QMutex::Recursive);  // 必须是递归的

void recursiveFunction(int level) {
   
   
    mutex.lock();  // 每次进入都加锁
    if (level > 0) {
   
   
        recursiveFunction(level - 1);
    }
    mutex.unlock();  // 每次退出都解锁
}

如果这里使用的是非递归锁,程序会在第二次 mutex.lock() 时死锁。

常见误区:

  • 不能用非递归互斥锁做递归加锁:如果一个线程反复调用 lock() 而不解锁,非递归互斥锁会导致程序死锁。
  • isRecursive() 只是一个查询函数,不会改变锁的行为。

1.2.2 bool QMutex::tryLock(int timeout = 0)
参数说明:

  • timeout:尝试加锁的最大等待时间(毫秒)。如果为 0(默认),表示非阻塞尝试,立即返回。 如果为 -1,表示无限等待直到锁可用(与 lock() 类似)。

返回值:

  • true:成功获得锁。
  • false:未能获得锁(可能是锁被其他线程占用,超时未成功)。

使用示例
非阻塞尝试加锁(默认参数)

QMutex mutex;

void someFunction() {
   
   
    if (mutex.tryLock()) {
   
   
        // 获得锁,执行线程安全操作
        // ...

        mutex.unlock();  // 解锁
    } else {
   
   
        // 没有获得锁,可能正在被其他线程占用
        qDebug() << "Mutex is currently locked by another thread.";
    }
}

阻塞等待最多 100 毫秒

QMutex mutex;

void someFunction() {
   
   
    if (mutex.tryLock(100)) {
   
     // 最多等待 100 毫秒
        // 获得锁,执行操作
        mutex.unlock();
    } else {
   
   
        // 超时未获得锁
        qDebug() << "Failed to acquire mutex within 100ms.";
    }
}

无限等待(等同于 lock())

QMutex mutex;

void someFunction() {
   
   
    if (mutex.tryLock(-1)) {
   
   
        // 永久等待直到获得锁
        mutex.unlock();
    }
}

使用建议:

  • tryLock() 非常适合用于不确定是否能获得锁、但不希望阻塞线程的场景,如 GUI 线程、定时器事件等。
  • 可以避免死锁风险或长时间卡顿,提高程序响应性。
  • tryLock(timeout) 是一个比 lock() 更灵活的接口。
  • 如果成功加锁,一定要配对调用 unlock(),否则会导致锁一直占用。

1.2.3 bool QMutex::tryLock()
函数说明:
它是一种非阻塞的加锁方式,相比于 lock() 方法(如果锁已被其他线程持有会阻塞等待),tryLock() 更适用于你想“尝试”加锁而不想阻塞当前线程的场景。

返回值:

  • true:加锁成功,当前线程获得了锁。
  • false:加锁失败,说明锁当前已被其他线程持有。

使用场景:

  • 避免死锁:在多个锁的情况下使用 tryLock() 可以检测是否能获得锁,从而避免死锁。
  • 提高响应性:在 UI 线程中避免阻塞等待。
  • 轮询或限时等待场景的辅助使用。

示例代码:

#include <QMutex>
#include <QDebug>

QMutex mutex;

void tryAccessSharedResource() {
   
   
    if (mutex.tryLock()) {
   
   
        qDebug() << "Got the lock, accessing shared resource.";
        // 执行共享资源访问操作
        mutex.unlock();
    } else {
   
   
        qDebug() << "Could not get the lock, skipping.";
    }
}

1.2.4 bool QMutex::try_lock_for(std::chrono::duration<Rep, Period> rel_time)
功能说明:

  • 尝试在指定时间段内加锁。
  • 如果在 rel_time 时间内获得锁,则返回 true。
  • 如果超时未获取到锁,返回 false。
  • 期间线程会阻塞,但最长不超过 rel_time。

使用示例:

#include <QDebug>
#include <QMutex>
#include <chrono>
#include <thread>

QMutex mutex;

void tryLockForExample(int id)
{
   
   
    if (mutex.try_lock_for(std::chrono::microseconds(300))) {
   
   
        qDebug() << "Thread:" << id << "got the lock";
        std::this_thread::sleep_for(std::chrono::seconds(1));  // 模拟任务
        mutex.unlock();
    } else {
   
   
        qDebug() << "Thread:" << id << "timed out";
    }
}

int main()
{
   
   
    std::thread t1(tryLockForExample, 1);
    std::thread t2(tryLockForExample, 2);

    t1.join();
    t2.join();

    return 0;
}

输出结果:

Thread: 1 got the lock
Thread: 2 timed out

使用以下单位来自定义等待时间:

std::chrono::milliseconds(100)  // 毫秒
std::chrono::seconds(1)         // 秒
std::chrono::microseconds(500)  // 微秒

注意事项:

  • 成功加锁后一定要 unlock()。
  • try_lock_for() 是阻塞的,但最多等待 rel_time 时间。
  • 避免滥用过短或过长的等待时间,防止浪费资源或性能抖动。

1.2.5 bool QMutex::try_lock_until(std::chrono::time_point<Clock, Duration> time_point)
功能说明:

  • 尝试在指定的绝对时间点之前 获取互斥锁。
  • 若在 abs_time 之前加锁成功,返回 true。
  • 否则(超时后仍未加锁),返回 false。
  • 相比 try_lock_for(duration) 是基于时间段,try_lock_until(time_point) 是基于时刻点。

参数说明:

  • abs_time:表示截止时间,如 std::chrono::steady_clock::now() + std::chrono::seconds(2)

返回值:

  • true:成功在截止时间之前获得锁。
  • false:到时间未获取锁。

示例代码:

#include <QDebug>
#include <QMutex>
#include <chrono>
#include <thread>

QMutex mutex;

void tryLockUntilExample(int id)
{
   
   
    // 截至时间为当前时间 + 500ms
    auto deaLine = std::chrono::steady_clock::now() + std::chrono::milliseconds(500);

    if (mutex.try_lock_until(deaLine)) {
   
   
        qDebug() << "Thread:" << id << "got the lock";
        std::this_thread::sleep_for(std::chrono::seconds(1)); // 模拟任务
        mutex.unlock();
    } else {
   
   
        qDebug() << "Thread:" << id << "timed out";
    }
}

int main()
{
   
   
    std::thread t1(tryLockUntilExample, 1);
    std::thread t2(tryLockUntilExample, 2);

    t1.join();
    t2.join();
    
    return a.exec();
}

可以使用不同的时钟源生成时间点:

  • std::chrono::steady_clock::now():推荐(不会倒退)
  • std::chrono::system_clock::now():可用于基于真实时间的场景

示例:

auto deadline = std::chrono::steady_clock::now() + std::chrono::milliseconds(300);

对比:try_lock_for() vs try_lock_until()
在这里插入图片描述

1.3 线程安全的示例

两个线程往同一个共享列表中添加数据,使用 QMutex 保证不会发生数据竞争。

worker.h

#ifndef WORKER_H
#define WORKER_H

#include <QThread>
#include <QMutex>
#include <QList>

class Worker : public QThread {
   
   
    Q_OBJECT
public:
    Worker(QList<int> *list, QMutex *mutex, int startValue, QObject *parent = nullptr);

protected:
    void run() override;

private:
    QList<int> *m_list;
    QMutex *m_mutex;
    int m_startValue;
};
#endif // WORKER_H

worker.cpp

#include "worker.h"
#include <QDebug>

Worker::Worker(QList<int> *list, QMutex *mutex, int startValue, QObject *parent)
    : QThread(parent), m_list(list), m_mutex(mutex), m_startValue(startValue)
{
   
   }

void Worker::run() {
   
   
    for (int i = 0; i < 5; ++i) {
   
   
        m_mutex->lock(); // 加锁
        int value = m_startValue + i;
        m_list->append(value);
        qDebug() << "Thread" << QThread::currentThreadId() << "added" << value;
        m_mutex->unlock(); // 解锁

        msleep(100); // 模拟耗时操作
    }
}

main.cpp

#include <QCoreApplication>
#include "worker.h"
#include <QList>
#include <QMutex>
#include <QDebug>

int main(int argc, char *argv[])
{
   
   
    QCoreApplication a(argc, argv);
    
    QList<int> sharedList;
    QMutex mutex;

    Worker thread1(&sharedList, &mutex, 100); // 添加100-104
    Worker thread2(&sharedList, &mutex, 200); // 添加200-204

    thread1.start();
    thread2.start();

    thread1.wait(); // 等待线程结束
    thread2.wait();

    qDebug() << "Final list:" << sharedList;

    return 0;
}

输出结果:

Thread 0x7fabc1d89740 added 100
Thread 0x7fabc1d898c0 added 200
Thread 0x7fabc1d89740 added 101
Thread 0x7fabc1d898c0 added 201
...
Final list: (100, 200, 101, 201, 102, 202, 103, 203, 104, 204)

列表数据交替出现,说明线程之间安全地访问了共享资源。

二、QMutexLocker 简介

  QMutexLocker 是 Qt 中的一个 RAII(资源获取即初始化)风格的互斥锁管理类,用于自动加锁和解锁 QMutex,可以帮助你防止忘记调用 unlock() 导致的死锁问题。

主要功能:
在这里插入图片描述
常规使用示例:

#include <QMutex>
#include <QMutexLocker>
#include <QDebug>

QMutex mutex;

void myFunction() {
   
   
    QMutexLocker locker(&mutex);  // 自动加锁
    qDebug() << "正在访问共享资源";
    // 退出作用域 locker 被析构时自动释放锁
}

生命周期管理(RAII):

{
   
   
    QMutexLocker locker(&mutex);  // 加锁
    // 在此作用域内 mutex 被锁定
} // 离开作用域 locker 析构,mutex 自动解锁 ✅

成员函数:
在这里插入图片描述
unlock()/relock() 示例:

QMutexLocker locker(&mutex);
locker.unlock();    // 手动解锁
// do something without lock
locker.relock();    // 重新加锁

注意事项:

  • 必须传入指针 &mutex,不能传引用或值。
  • QMutexLocker 不支持超时加锁(想用超时应使用 QMutex::tryLock(timeout))。
  • 如果你使用了 unlock(),要记得使用 relock(),否则析构时不会自动释放锁。

2.1 线程安全的示例

worker.cpp(修改后的)

#include "worker.h"
#include <QDebug>
#include <QMutexLocker>

Worker::Worker(QList<int> *list, QMutex *mutex, int startValue, QObject *parent)
    : QThread(parent), m_list(list), m_mutex(mutex), m_startValue(startValue)
{
   
   }

void Worker::run() {
   
   
    for (int i = 0; i < 
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值