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 < 5; ++i) {
        {
            QMutexLocker locker(m_mutex); // 自动加锁并在作用域结束时释放锁
            int value = m_startValue + i;
            m_list->append(value);
            qDebug() << "Thread" << QThread::currentThreadId() << "added" << value;
        } // locker 超出作用域,自动解锁

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

其他文件(worker.h、main.cpp)保持不变。

三、QReadWriteLock 简介

  QReadWriteLock 是 Qt 提供的一个 读写锁,允许多个线程同时读取共享资源,但当有线程写入时,会独占该资源。它适用于读多写少的多线程场景,相比 QMutex 可显著提高并发性能。

核心特点:
在这里插入图片描述
常用函数:
在这里插入图片描述
基本使用示例:

#include <QReadWriteLock>
#include <QDebug>
#include <QThread>

QReadWriteLock rwLock;

int sharedData = 0;

void readFunction() {
    rwLock.lockForRead();
    qDebug() << "Reading:" << sharedData;
    rwLock.unlock();
}

void writeFunction() {
    rwLock.lockForWrite();
    sharedData++;
    qDebug() << "Writing:" << sharedData;
    rwLock.unlock();
}

与 QReadLocker / QWriteLocker 搭配使用
RAII 风格自动加锁与释放锁:

#include <QReadWriteLock>
#include <QReadLocker>
#include <QWriteLocker>

QReadWriteLock rwLock;

void readFunc() {
    QReadLocker locker(&rwLock);  // 自动加读锁
    qDebug() << "Reading data";
    // 自动释放锁
}

void writeFunc() {
    QWriteLocker locker(&rwLock); // 自动加写锁
    qDebug() << "Writing data";
    // 自动释放锁
}

tryLock 示例(防阻塞):

if (rwLock.tryLockForRead(100)) {
    // 成功加锁
    qDebug() << "Got read lock";
    rwLock.unlock();
} else {
    qDebug() << "Read lock timeout";
}

应用场景:
在这里插入图片描述
注意事项:

  • 不能同时加锁读写,否则会死锁或不确定行为。
  • 如果写操作频繁,QReadWriteLock 的优势会被抵消,此时应使用 QMutex。
  • 默认构造为非递归锁,如有递归需求需指定:

3.1 完整示例

#include <QCoreApplication>
#include <QThread>
#include <QReadWriteLock>
#include <QReadLocker>
#include <QWriteLocker>
#include <QDebug>

// 共享数据和读写锁
int sharedValue = 0;
QReadWriteLock rwLock;

// 读线程
class ReaderThread : public QThread {
    void run() override {
        for (int i = 0; i < 10; ++i) {
            QReadLocker locker(&rwLock);  // 自动加读锁
            qDebug() << "[Reader]" << QThread::currentThreadId() << "read value:" << sharedValue;
            QThread::msleep(100);
        }
    }
};

// 写线程
class WriterThread : public QThread {
    void run() override {
        for (int i = 0; i < 5; ++i) {
            QWriteLocker locker(&rwLock);  // 自动加写锁
            sharedValue += 1;
            qDebug() << ">>> [Writer]" << QThread::currentThreadId() << "updated value to:" << sharedValue;
            QThread::msleep(300);
        }
    }
};

int main(int argc, char *argv[]) {
    QCoreApplication app(argc, argv);

    // 创建多个读线程和一个写线程
    ReaderThread reader1, reader2, reader3;
    WriterThread writer;

    reader1.start();
    reader2.start();
    reader3.start();
    writer.start();

    reader1.wait();
    reader2.wait();
    reader3.wait();
    writer.wait();

    return 0;
}

运行效果示例:

[Reader] 0x12c read value: 0
[Reader] 0x130 read value: 0
[Reader] 0x134 read value: 0
>>> [Writer] 0x138 updated value to: 1
[Reader] 0x12c read value: 1
[Reader] 0x130 read value: 1
[Reader] 0x134 read value: 1
>>> [Writer] 0x138 updated value to: 2

关键点说明:

  • QReadLocker 和 QWriteLocker 实现了 RAII 自动加解锁,推荐使用。
  • 多个读线程可以同时运行,写线程会等待读锁释放。
  • 写锁是独占的,一旦获取写锁,其他线程(读或写)都会被阻塞。

四、QSemaphore 简介

  QSemaphore 是 Qt 中提供的一种计数信号量(Counting Semaphore),主要用于线程之间的资源访问控制。它可以控制多个线程对有限资源的并发访问,比如:连接池、任务队列、并发下载等。

信号量就像一个资源计数器:

  • 初始时表示有多少资源可用。
  • acquire(n) 表示申请 n 个资源,如果不够则阻塞。
  • release(n) 表示释放 n 个资源,唤醒等待线程。

主要函数:
在这里插入图片描述
使用示例:
模拟最多允许 3 个线程并发运行任务

#include <QCoreApplication>
#include <QThread>
#include <QSemaphore>
#include <QDebug>

QSemaphore semaphore(3);  // 最多允许 3 个线程并发

class WorkerThread : public QThread {
    int id;
public:
    WorkerThread(int id) : id(id) {}

    void run() override {
        qDebug() << "Thread" << id << "尝试获取信号量...";
        semaphore.acquire();  // 获取一个许可(如没资源就阻塞)
        qDebug() << "Thread" << id << "开始工作";
        QThread::sleep(2);    // 模拟耗时操作
        qDebug() << "Thread" << id << "释放信号量";
        semaphore.release();  // 释放许可
    }
};

int main(int argc, char *argv[]) {
    QCoreApplication a(argc, argv);

    QList<WorkerThread*> threads;
    for (int i = 0; i < 6; ++i) {
        auto t = new WorkerThread(i + 1);
        t->start();
        threads.append(t);
    }

    for (auto t : threads)
        t->wait();

    return 0;
}

示例运行输出(并发最多3个线程):

Thread 1 尝试获取信号量...
Thread 1 开始工作
Thread 2 尝试获取信号量...
Thread 2 开始工作
Thread 3 尝试获取信号量...
Thread 3 开始工作
Thread 4 尝试获取信号量...
Thread 5 尝试获取信号量...
Thread 6 尝试获取信号量...
Thread 1 释放信号量
Thread 4 开始工作
Thread 2 释放信号量
Thread 5 开始工作

4.1 封装成线程池类(如 SimpleThreadPool)

先实现一个简单的 SimpleThreadPool 类,使用 QSemaphore 控制线程并发数。

simplethreadpool.h

#pragma once

#include <QSemaphore>
#include <QThread>
#include <QVector>
#include <functional>
#include <QObject>

class Worker : public QThread {
public:
    Worker(QSemaphore* sem, std::function<void()> task);
    void run() override;

private:
    QSemaphore* m_semaphore;
    std::function<void()> m_task;
};

class SimpleThreadPool : public QObject {
    Q_OBJECT
public:
    explicit SimpleThreadPool(int maxThreads, QObject* parent = nullptr);
    void submit(std::function<void()> task);
    void waitForDone();

private:
    QSemaphore m_semaphore;
    QVector<Worker*> m_workers;
};

simplethreadpool.cpp

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

Worker::Worker(QSemaphore* sem, std::function<void()> task)
    : m_semaphore(sem), m_task(std::move(task)) {}

void Worker::run() {
    m_semaphore->acquire();
    m_task();  // 执行传入的任务
    m_semaphore->release();
}

SimpleThreadPool::SimpleThreadPool(int maxThreads, QObject* parent)
    : QObject(parent), m_semaphore(maxThreads) {}

void SimpleThreadPool::submit(std::function<void()> task) {
    Worker* worker = new Worker(&m_semaphore, std::move(task));
    m_workers.append(worker);
    worker->start();
}

void SimpleThreadPool::waitForDone() {
    for (Worker* worker : m_workers) {
        worker->wait();
        delete worker;
    }
    m_workers.clear();
}

main.cpp

#include <QCoreApplication>
#include <QDebug>
#include <QRandomGenerator>
#include "simplethreadpool.h"

int main(int argc, char *argv[]) {
    QCoreApplication app(argc, argv);

    SimpleThreadPool pool(3);  // 最多 3 个线程并发

    for (int i = 0; i < 10; ++i) {
        int taskId = i + 1;
        pool.submit([taskId]() {
            qDebug() << "任务" << taskId << "开始执行";
            QThread::msleep(QRandomGenerator::global()->bounded(1000, 3000));
            qDebug() << "任务" << taskId << "完成";
        });
    }

    pool.waitForDone();
    qDebug() << "所有任务执行完毕。";
    return 0;
}

4.1.2 基于 QSemaphore 的并发下载器(多线程下载模拟器)

downloader.h

#pragma once

#include <QThread>
#include <QSemaphore>
#include <QString>

class Downloader : public QThread {
    Q_OBJECT
public:
    Downloader(QSemaphore* sem, const QString& url, const QString& savePath);
    void run() override;

private:
    QSemaphore* m_semaphore;
    QString m_url;
    QString m_savePath;
};

downloader.cpp

#include "downloader.h"
#include <QNetworkAccessManager>
#include <QNetworkReply>
#include <QEventLoop>
#include <QFile>
#include <QDebug>

Downloader::Downloader(QSemaphore* sem, const QString& url, const QString& savePath)
    : m_semaphore(sem), m_url(url), m_savePath(savePath) {}

void Downloader::run() {
    m_semaphore->acquire();  // 获取许可
    qDebug() << "开始下载:" << m_url;

    QNetworkAccessManager manager;
    QNetworkReply* reply = manager.get(QNetworkRequest(QUrl(m_url)));

    QEventLoop loop;
    QObject::connect(reply, &QNetworkReply::finished, &loop, &QEventLoop::quit);
    loop.exec();

    QFile file(m_savePath);
    if (file.open(QIODevice::WriteOnly)) {
        file.write(reply->readAll());
        file.close();
        qDebug() << "保存完成:" << m_savePath;
    } else {
        qDebug() << "保存失败:" << m_savePath;
    }

    reply->deleteLater();
    m_semaphore->release();  // 释放许可
}

main.cpp

#include <QCoreApplication>
#include <QSemaphore>
#include "downloader.h"

int main(int argc, char *argv[]) {
    QCoreApplication app(argc, argv);

    // 最多允许 3 个同时下载
    QSemaphore semaphore(3);

    QStringList urls = {
        "https://www.example.com/index.html",
        "https://www.qt.io/favicon.ico",
        "https://raw.githubusercontent.com/github/explore/main/topics/qt/qt.png",
        "https://raw.githubusercontent.com/github/explore/main/topics/cpp/cpp.png"
    };

    QList<Downloader*> downloaders;
    for (int i = 0; i < urls.size(); ++i) {
        QString savePath = QString("file_%1").arg(i + 1);
        if (urls[i].endsWith(".png"))
            savePath += ".png";
        else if (urls[i].endsWith(".ico"))
            savePath += ".ico";
        else
            savePath += ".html";

        Downloader* d = new Downloader(&semaphore, urls[i], savePath);
        downloaders.append(d);
        d->start();
    }

    // 等待所有线程结束
    for (Downloader* d : downloaders) {
        d->wait();
        delete d;
    }

    qDebug() << "所有文件下载完成。";
    return 0;
}

输出示例:

开始下载: https://www.example.com/index.html
开始下载: https://www.qt.io/favicon.ico
开始下载: https://raw.githubusercontent.com/github/explore/main/topics/qt/qt.png
保存完成: file_1.html
保存完成: file_2.ico
保存完成: file_3.png
开始下载: https://raw.githubusercontent.com/github/explore/main/topics/cpp/cpp.png
保存完成: file_4.png
所有文件下载完成。

4.1.3 基于 Qt GUI 的并发下载器(带按钮和进度条)

DownloadTask 类(负责下载 + 发信号)

downloadtask.h

#pragma once

#include <QObject>
#include <QSemaphore>
#include <QUrl>

class QNetworkReply;

class DownloadTask : public QObject {
    Q_OBJECT
public:
    DownloadTask(QSemaphore* sem, const QUrl& url, int taskId);
    void start();

signals:
    void progress(int taskId, int percent);
    void finished(int taskId, const QString& filePath);
    void failed(int taskId, const QString& reason);

private:
    QSemaphore* m_semaphore;
    QUrl m_url;
    int m_taskId;
};

downloadtask.cpp

#include "downloadtask.h"
#include <QNetworkAccessManager>
#include <QNetworkReply>
#include <QEventLoop>
#include <QFile>
#include <QDebug>
#include <QThread>

DownloadTask::DownloadTask(QSemaphore* sem, const QUrl& url, int taskId)
    : m_semaphore(sem), m_url(url), m_taskId(taskId) {}

void DownloadTask::start() {
    m_semaphore->acquire();

    QNetworkAccessManager* manager = new QNetworkAccessManager(this);
    QNetworkReply* reply = manager->get(QNetworkRequest(m_url));

    QObject::connect(reply, &QNetworkReply::downloadProgress, this,
                     [=](qint64 received, qint64 total) {
        int percent = total > 0 ? (received * 100 / total) : 0;
        emit progress(m_taskId, percent);
    });

    QObject::connect(reply, &QNetworkReply::finished, this, [=]() {
        QString fileName = QString("task_%1.dat").arg(m_taskId);
        QFile file(fileName);
        if (file.open(QIODevice::WriteOnly)) {
            file.write(reply->readAll());
            file.close();
            emit finished(m_taskId, fileName);
        } else {
            emit failed(m_taskId, "无法保存文件");
        }

        reply->deleteLater();
        m_semaphore->release();
        this->deleteLater();
    });

    QObject::connect(reply, &QNetworkReply::errorOccurred, this,
        [=](QNetworkReply::NetworkError code) {
            emit failed(m_taskId, reply->errorString());
        });
}

MainWindow 控制逻辑

mainwindow.h

#pragma once

#include <QMainWindow>
#include <QSemaphore>
#include <QMap>

QT_BEGIN_NAMESPACE
namespace Ui { class MainWindow; }
QT_END_NAMESPACE

class QProgressBar;
class DownloadTask;

class MainWindow : public QMainWindow {
    Q_OBJECT

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

private slots:
    void on_startButton_clicked();
    void handleProgress(int taskId, int percent);
    void handleFinished(int taskId, const QString& path);
    void handleFailed(int taskId, const QString& reason);

private:
    Ui::MainWindow *ui;
    QSemaphore m_semaphore;
    int m_nextTaskId = 1;
    QMap<int, QProgressBar*> m_taskBars;
};

mainwindow.cpp

#include "mainwindow.h"
#include "ui_mainwindow.h"
#include "downloadtask.h"

#include <QProgressBar>
#include <QVBoxLayout>
#include <QThread>
#include <QDebug>

MainWindow::MainWindow(QWidget *parent)
    : QMainWindow(parent),
      ui(new Ui::MainWindow),
      m_semaphore(3) // 限制并发任务数
{
    ui->setupUi(this);
}

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

void MainWindow::on_startButton_clicked() {
    QStringList urlList = {
        "https://raw.githubusercontent.com/github/explore/main/topics/qt/qt.png",
        "https://www.qt.io/favicon.ico",
        "https://raw.githubusercontent.com/github/explore/main/topics/cpp/cpp.png",
        "https://www.example.com/index.html"
    };

    for (const QString& url : urlList) {
        int taskId = m_nextTaskId++;

        QProgressBar* bar = new QProgressBar();
        bar->setRange(0, 100);
        bar->setValue(0);
        ui->verticalLayout->addWidget(bar);
        m_taskBars[taskId] = bar;

        DownloadTask* task = new DownloadTask(&m_semaphore, QUrl(url), taskId);
        QThread* thread = QThread::create([task]() { task->start(); });
        task->moveToThread(thread);

        connect(task, &DownloadTask::progress, this, &MainWindow::handleProgress);
        connect(task, &DownloadTask::finished, this, &MainWindow::handleFinished);
        connect(task, &DownloadTask::failed, this, &MainWindow::handleFailed);

        connect(thread, &QThread::finished, thread, &QObject::deleteLater);
        connect(task, &QObject::destroyed, thread, &QThread::quit);

        thread->start();
    }
}

void MainWindow::handleProgress(int taskId, int percent) {
    if (m_taskBars.contains(taskId)) {
        m_taskBars[taskId]->setValue(percent);
    }
}

void MainWindow::handleFinished(int taskId, const QString& path) {
    if (m_taskBars.contains(taskId)) {
        m_taskBars[taskId]->setFormat(QString("完成: %1").arg(path));
        m_taskBars[taskId]->setValue(100);
    }
}

void MainWindow::handleFailed(int taskId, const QString& reason) {
    if (m_taskBars.contains(taskId)) {
        m_taskBars[taskId]->setFormat(QString("失败: %1").arg(reason));
    }
}

五、QWaitCondition 简介

  QWaitCondition 是 Qt 提供的线程同步机制,常用于线程间的等待与唤醒,配合 QMutex 或 QReadWriteLock 使用。它可以让线程主动等待某个条件成立,直到被另一个线程唤醒。让线程进入等待状态,直到被其他线程通过 wakeOne() 或 wakeAll() 唤醒,适用于生产者-消费者、资源等待等多线程协作场景。

5.1 常用函数

5.1.1 void QWaitCondition::wakeAll()
功能:

  • 唤醒所有因为调用 wait() 而挂起的线程。
  • 唤醒后,这些线程会竞争获取互斥锁,然后继续执行。

常用于:广播通知多个等待线程某个条件已满足,例如缓冲区被填充、任务队列非空等。

示例:多个线程等待条件,wakeAll() 唤醒全部

#include <QCoreApplication>
#include <QThread>
#include <QMutex>
#include <QWaitCondition>
#include <QDebug>

QMutex mutex;
QWaitCondition condition;
bool ready = false;

class Worker : public QThread {
public:
    Worker(int id) : m_id(id) {}

    void run() override {
        mutex.lock();
        while (!ready)
            condition.wait(&mutex);  // 挂起直到被唤醒

        qDebug() << "线程" << m_id << "被唤醒";
        mutex.unlock();
    }

private:
    int m_id;
};

int main(int argc, char *argv[]) {
    QCoreApplication a(argc, argv);

    QList<Worker*> workers;
    for (int i = 0; i < 3; ++i) {
        auto* w = new Worker(i);
        w->start();
        workers.append(w);
    }

    qDebug() << "主线程休眠 2 秒后唤醒所有线程...";
    QThread::sleep(2);

    mutex.lock();
    ready = true;
    condition.wakeAll();  // 等价于 notify_all()
    mutex.unlock();

    for (Worker* w : workers)
        w->wait();

    return 0;
}

输出示例:

主线程休眠 2 秒后唤醒所有线程...
线程 0 被唤醒
线程 1 被唤醒
线程 2 被唤醒

5.1.2 void QWaitCondition::wakeOne()
功能:

  • 唤醒一个当前因 wait() 挂起的线程,使其恢复执行。
  • 被唤醒的线程将尝试重新获取 QMutex,然后从 wait() 后继续执行。

常用场景:

  • 生产者-消费者模型中,生产者每次生产后通知一个消费者开始消费。
  • 任务调度系统中,任务就绪时唤醒一个等待线程来处理。

使用方式说明:
搭配方式:
必须和 QMutex 配合使用,通常流程是:

mutex.lock();
while (!条件成立)
    condition.wait(&mutex);
...
mutex.unlock();

然后另一个线程在更新条件后:

mutex.lock();
... // 改变条件
condition.wakeOne();
mutex.unlock();

示例:一个线程等待、另一个线程唤醒

#include <QCoreApplication>
#include <QThread>
#include <QMutex>
#include <QWaitCondition>
#include <QDebug>

QMutex mutex;
QWaitCondition condition;
bool ready = false;

class WaitingThread : public QThread {
public:
    void run() override {
        mutex.lock();
        qDebug() << "等待线程:等待信号...";
        while (!ready)
            condition.wait(&mutex);  // 等待唤醒

        qDebug() << "等待线程:被唤醒,开始处理任务";
        mutex.unlock();
    }
};

int main(int argc, char *argv[]) {
    QCoreApplication a(argc, argv);

    WaitingThread t;
    t.start();

    QThread::sleep(2); // 模拟主线程处理

    mutex.lock();
    ready = true;
    qDebug() << "主线程:准备就绪,唤醒等待线程";
    condition.wakeOne(); // 唤醒一个线程
    mutex.unlock();

    t.wait();

    return 0;
}

输出示例:

等待线程:等待信号...
主线程:准备就绪,唤醒等待线程
等待线程:被唤醒,开始处理任务

5.1.3 bool QWaitCondition::wait(QMutex *lockedMutex, unsigned long time = ULONG_MAX)

参数说明:

  • lockedMutex:必须是当前线程已经锁住的互斥锁,函数内部会暂时释放它
  • time:等待的最大时间(毫秒),默认值 ULONG_MAX 表示无限等待,直到被唤醒

函数行为:

  1. 调用前必须加锁 QMutex。
  2. wait() 会:自动释放锁;让线程进入等待状态;等待其他线程调用 wakeOne() 或 wakeAll(); 被唤醒后自动重新获取锁,再继续执行。
  3. 返回值:true:被正常唤醒;false:超时返回。

示例:线程等待条件成立

#include <QCoreApplication>
#include <QThread>
#include <QMutex>
#include <QWaitCondition>
#include <QDebug>

QMutex mutex;
QWaitCondition condition;
bool ready = false;

class Worker : public QThread {
public:
    void run() override {
        mutex.lock();
        qDebug() << "子线程:等待 ready = true ...";
        while (!ready)
            condition.wait(&mutex);  // 进入等待,自动释放 mutex

        qDebug() << "子线程:被唤醒,继续工作";
        mutex.unlock();
    }
};

int main(int argc, char *argv[]) {
    QCoreApplication a(argc, argv);

    Worker thread;
    thread.start();

    QThread::sleep(2); // 模拟主线程等待或准备

    mutex.lock();
    ready = true;
    condition.wakeOne();  // 唤醒等待线程
    mutex.unlock();

    thread.wait();
    return 0;
}

输出示例:

子线程:等待 ready = true ...
子线程:被唤醒,继续工作

超时等待的使用
可以设置最大等待时间(例如 3000 毫秒):

if (!condition.wait(&mutex, 3000)) {
    qDebug() << "等待超时,退出";
}

5.1.4 bool QWaitCondition::wait(QReadWriteLock *lockedReadWriteLock, unsigned long time = ULONG_MAX)

参数说明:

  • lockedReadWriteLock:当前线程已加锁的 QReadWriteLock(必须处于写锁状态)
  • time:等待时间,单位为毫秒,默认为 ULONG_MAX(无限等待)

注意:只能传入写锁(即调用 lockForWrite() 后的 QReadWriteLock),否则行为未定义。

使用示例:

#include <QCoreApplication>
#include <QThread>
#include <QWaitCondition>
#include <QReadWriteLock>
#include <QDebug>

QReadWriteLock rwLock;
QWaitCondition condition;
bool ready = false;

class Worker : public QThread {
public:
    void run() override {
        rwLock.lockForWrite();
        qDebug() << "线程:等待 ready = true ...";

        while (!ready) {
            if (!condition.wait(&rwLock, 5000)) { // 最多等待 5 秒
                qDebug() << "线程:等待超时";
                rwLock.unlock();
                return;
            }
        }

        qDebug() << "线程:被唤醒,继续处理";
        rwLock.unlock();
    }
};

int main(int argc, char *argv[]) {
    QCoreApplication a(argc, argv);

    Worker thread;
    thread.start();

    QThread::sleep(2); // 模拟准备

    rwLock.lockForWrite();
    ready = true;
    condition.wakeOne();
    rwLock.unlock();

    thread.wait();
    return 0;
}

输出示例:

线程:等待 ready = true ...
线程:被唤醒,继续处理

注意事项:必须使用 lockForWrite() 获得写锁后再调用 wait() 使用读锁 lockForRead() 调用 wait()(会崩溃或死锁)

构建一个基于 QReadWriteLock + QWaitCondition 的线程池任务管理模型。
示例目标:
实现一个线程池任务调度器:

  • 多个工作线程不断从任务队列中获取任务执行;
  • 如果队列为空,工作线程进入 wait() 状态;
  • 主线程或其他线程添加任务后通过 wakeOne() 唤醒等待线程;
  • 使用 QReadWriteLock 保护任务队列(读锁读任务,写锁添加/移除任务);
  • 使用 QWaitCondition 实现线程同步。

类结构概览:

class TaskThread : public QThread  // 工作线程
class ThreadPoolManager : public QObject  // 任务调度器(添加任务 + 唤醒线程)

完整示例代码:

#include <QCoreApplication>
#include <QThread>
#include <QReadWriteLock>
#include <QWaitCondition>
#include <QQueue>
#include <QDebug>
#include <functional>

using Task = std::function<void()>; // 定义任务类型

class ThreadPoolManager;

class TaskThread : public QThread {
public:
    TaskThread(ThreadPoolManager* manager, QObject* parent = nullptr)
        : QThread(parent), m_manager(manager) {}

protected:
    void run() override;

private:
    ThreadPoolManager* m_manager;
};

class ThreadPoolManager : public QObject {
    Q_OBJECT
public:
    ThreadPoolManager(QObject* parent = nullptr) : QObject(parent), m_stopped(false) {}

    void startThreads(int count = 4) {
        for (int i = 0; i < count; ++i) {
            TaskThread* thread = new TaskThread(this);
            m_threads.append(thread);
            thread->start();
        }
    }

    void stopAll() {
        m_lock.lockForWrite();
        m_stopped = true;
        m_condition.wakeAll();
        m_lock.unlock();

        for (auto thread : m_threads) {
            thread->wait();
            delete thread;
        }
        m_threads.clear();
    }

    void addTask(Task task) {
        m_lock.lockForWrite();
        m_tasks.enqueue(task);
        m_condition.wakeOne();
        m_lock.unlock();
    }

    Task takeTask() {
        m_lock.lockForWrite();
        while (m_tasks.isEmpty() && !m_stopped) {
            m_condition.wait(&m_lock);  // 线程阻塞等待
        }

        Task task;
        if (!m_tasks.isEmpty()) {
            task = m_tasks.dequeue();
        }
        m_lock.unlock();
        return task;
    }

    bool isStopped() const {
        return m_stopped;
    }

private:
    QList<TaskThread*> m_threads;
    QQueue<Task> m_tasks;
    QReadWriteLock m_lock;
    QWaitCondition m_condition;
    bool m_stopped;

    friend class TaskThread;
};

void TaskThread::run() {
    while (!m_manager->isStopped()) {
        Task task = m_manager->takeTask();
        if (task) {
            task();  // 执行任务
        }
    }
}

示例主函数:测试线程池

int main(int argc, char *argv[]) {
    QCoreApplication a(argc, argv);

    ThreadPoolManager pool;
    pool.startThreads(4);  // 启动4个线程

    // 添加5个任务
    for (int i = 0; i < 5; ++i) {
        pool.addTask([i]() {
            qDebug() << "执行任务" << i << "线程ID:" << QThread::currentThread();
            QThread::sleep(1);
        });
    }

    QThread::sleep(7);
    pool.stopAll();

    return 0;
}

输出示例:

执行任务 0 线程ID: 0x55cd6dd5c740
执行任务 1 线程ID: 0x55cd6dd5c8a0
执行任务 2 线程ID: 0x55cd6dd5c740
执行任务 3 线程ID: 0x55cd6dd5c8a0
执行任务 4 线程ID: 0x55cd6dd5c740

5.2 使用场景:生产者-消费者模型

示例代码:生产者-消费者(缓冲区)

#include <QCoreApplication>
#include <QThread>
#include <QMutex>
#include <QWaitCondition>
#include <QQueue>
#include <QDebug>

QQueue<int> buffer;
QMutex mutex;
QWaitCondition bufferNotEmpty;
QWaitCondition bufferNotFull;
const int maxSize = 5;

class Producer : public QThread {
    void run() override {
        for (int i = 1; i <= 10; ++i) {
            mutex.lock();
            while (buffer.size() >= maxSize)
                bufferNotFull.wait(&mutex);  // 缓冲满就等待

            buffer.enqueue(i);
            qDebug() << "生产:" << i;

            bufferNotEmpty.wakeOne();  // 通知消费者
            mutex.unlock();

            QThread::sleep(1);
        }
    }
};

class Consumer : public QThread {
    void run() override {
        for (int i = 1; i <= 10; ++i) {
            mutex.lock();
            while (buffer.isEmpty())
                bufferNotEmpty.wait(&mutex);  // 缓冲空就等待

            int val = buffer.dequeue();
            qDebug() << "消费:" << val;

            bufferNotFull.wakeOne();  // 通知生产者
            mutex.unlock();

            QThread::sleep(2);
        }
    }
};

main.cpp

int main(int argc, char *argv[]) {
    QCoreApplication a(argc, argv);

    Producer producer;
    Consumer consumer;

    producer.start();
    consumer.start();

    producer.wait();
    consumer.wait();

    return 0;
}

输出示例:

生产:1
消费:1
生产:2
消费:2
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值