第十二章 多线程

Qt多线程编程

Qt多线程的核心类:QThread

  • QThread 是实现多线程的核心类。
  • 一般做法是从 QThread 继承,定义自己的线程类。

线程同步

  • 线程间的同步是线程交互的主要问题。
  • Qt 提供了多种同步机制,包括:
    • QMutex:用于互斥访问共享资源。
    • QWaitCondition:允许线程在满足特定条件时等待或通知其他线程。
    • QScmaphore(信号量):用于控制对共享资源的访问数量。

Qt Concurrent 模块

  • Qt Concurrent 模块提供了高级的 API,用于简化多线程编程。
  • 该模块使得开发者无需直接使用互斥量和信号量等基础操作,即可实现多线程编程。
  • Qt Concurrent 提供了任务分发、并行计算等功能,提高了开发效率和代码质量。

多线程编程中常用的类

类名 描述
QThread 创建和管理线程的类。
QRunnable 表示可以在 QThreadPool 中执行的任务类。
QThreadPool 管理多个线程池,高效复用线程。
QMutex 互斥量,用于保护共享资源,防止竞争条件。
QWaitCondition 用于在线程间同步,使一个线程等待另一个线程的信号。
QSemaphore 控制并发线程的数量,管理资源的访问。
QThreadStorage 提供线程专用的局部数据存储。
QTimer 定时器类,可用于在不同线程中执行定时操作。

QThread

QThread类是Qt框架中用于管理线程的类。

在多线程程序设计中,需要通过继承QThread并定义线程类,然后重写QThread的run()函数来处理线程的事件循环。

应用程序的主线程称为主线程,创建的其他线程称为工作线程。

线程创建

主线程负责创建工作线程,并通过调用工作线程的start()函数来启动线程的任务。start()函数内部会调用run()函数,从而进入工作线程的事件循环。

QThread类的主要功能

  • 事件循环:工作线程的run()函数通常包含一个无限循环,用于处理线程的主要任务。
  • 线程结束:工作线程的事件循环可以通过调用exit()或quit()函数来正常结束。另外,主线程也可以强制结束工作线程,通过调用terminate()函数。
  • 父类QObject的特性:由于QThread的父类是QObject,因此可以使用信号与槽机制进行通信。

QThread的信号

QThread类定义了两个重要信号:started()和finished()。

  1. started()信号:在线程开始运行前,即run()函数被调用之前被发射。
  2. finished()信号:在线程即将结束时被发射。

主要接口函数:

函数名 描述
void start(Priority priority) 启动线程,执行 run() 函数。
void quit() 请求线程结束。
void exit(int returnCode) 请求线程退出,并返回指定的退出代码。
virtual void run() 用户重载此函数以定义线程要执行的代码。
bool isRunning() const 返回线程是否正在运行。
bool isFinished() const 返回线程是否已结束。
void setPriority(Priority priority) 设置线程的优先级。
Priority priority() const 返回线程的当前优先级。
Q_SIGNAL void finished() 当线程运行结束时发出信号。
Q_SIGNAL void started() 当线程开始执行时发出信号。
static QThread* currentThread() 返回当前执行的线程指针。
static Qt::ThreadId currentThreadId() 返回当前线程的 ID。
  • 优先级: Priority 是一个枚举类型,表示线程的优先级,如 Lowest, Low, Normal, High, 和 Highest。
  • 信号: Q_SIGNAL 表示该函数是信号,能被其他对象连接以响应线程状态的变化。

示例:主要接口函数的使用示例,包括线程的创建、启动、管理和信号的连接

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

class Worker : public QThread {
    Q_OBJECT // 需要声明为QObject的子类以使用信号和槽

public:
    void run() override {
        for (int i = 0; i < 5; ++i) {
            qDebug() << "Running in thread:" << QThread::currentThreadId();
            QThread::sleep(1); // 模拟耗时操作
        }
        emit finished(); // 发出finished信号
    }

signals:
    void finished(); // 定义finished信号
};

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

    Worker worker;

    // 连接信号和槽
    QObject::connect(&worker, &Worker::finished, [&]() {
        qDebug() << "Worker thread finished.";
        a.quit(); // 结束程序
    });

    worker.start(); // 启动线程

    qDebug() << "Main thread ID:" << QThread::currentThreadId();

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

    return a.exec();
}

#include "main.moc" 

运行结果:

Main thread ID: 12345678
Running in thread: 87654321
Running in thread: 87654321
Running in thread: 87654321
Running in thread: 87654321
Running in thread: 87654321
Worker thread finished.

QThreadPool

QThreadPool 是 Qt 提供的一个线程池类,用于管理和复用线程。它可以有效地处理短时间的任务,避免了频繁的线程创建和销毁,从而提高了性能。

特性

  • 线程复用: QThreadPool 可以复用已有的线程,因此适合频繁提交短任务。
  • 自动管理:管理线程的数量和生命周期,开发者无需手动控制。
  • 易用性:通过 QRunnable 类提交任务,简化了多线程编程。

使用方法

  1. 创建自定义任务:继承 QRunnable 类并重载 run() 方法。
  2. 创建 QThreadPool 实例:通常使用全局的线程池或创建一个本地实例。
  3. 提交任务:使用 start() 方法将自定义任务提交到线程池中。

示例:如何使用 QThreadPool 和 QRunnable 类来并发执行多个任务。

#include <QCoreApplication>
#include <QThreadPool>
#include <QRunnable>
#include <QDebug>
#include <chrono>

class Task : public QRunnable {
public:
    Task(int id) : taskId(id) {}

    void run() override {
        for (int i = 0; i < 3; ++i) {
            qDebug() << "Task" << taskId << "running in thread:" << QThread::currentThread();
            std::this_thread::sleep_for(std::chrono::seconds(1)); // 模拟耗时操作
        }
        qDebug() << "Task" << taskId << "finished.";
    }

private:
    int taskId;
};

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

    QThreadPool pool; // 创建线程池

    // 提交多个任务
    for (int i = 0; i < 5; ++i) {
        Task *task = new Task(i); // 为每个任务创建实例
        pool.start(task); // 提交到线程池
    }

    pool.waitForDone(); // 等待所有任务完成

    return app.exec();
}

代码解析

  • Task 类:继承自 QRunnable ,重载 run() 方法,这里实现了简单的输出和延时,模拟了一个耗时操作。
  • 创建 QThreadPool:在 main() 函数中创建一个 QThreadPool 实例。
  • 提交任务:通过 start() 方法将多个 Task 对象提交到线程池中。
  • 等待完成:使用 waitForDone() 来等待所有任务完成执行。

运行结果:

Task 0 running in thread: QThread(0x12345678)
Task 1 running in thread: QThread(0x12345679)
Task 2 running in thread: QThread(0x1234567A)
...
Task 0 finished.
Task 1 finished.
...

QRunnable

QRunnable 是一个用于表示可运行任务的基类。它通常与 QThreadPool 一起使用,旨在简化多线程编程,特别是当你需要并发执行多个短任务时。

特性

  • 简单性:通过继承 QRunnable ,很容易定义一个可在不同线程中执行的任务。
  • 与 QThreadPool 结合使用:可以将 QRunnable 对象提交到 QThreadPool 中运行。
  • 支持可重用性:可以通过不同的线程执行相同的任务逻辑。

使用方法

  • 继承 QRunnable:创建一个子类并重载 run() 方法。
  • 提交任务:通过 QThreadPool 的 start() 方法提交实例。

示例:如何使用 QRunnable 和 QThreadPool 来并发执行多个任务。

#include <QCoreApplication>
#include <QThreadPool>
#include <QRunnable>
#include <QDebug>
#include <chrono>
#include <thread>

// 自定义任务类,继承自 QRunnable
class MyTask : public QRunnable {
public:
    MyTask(int id) : taskId(id) {}

    void run() override {
        qDebug() << "Task" << taskId << "is running in thread:" << QThread::currentThread();
        std::this_thread::sleep_for(std::chrono::seconds(1)); // 模拟耗时操作
        qDebug() << "Task" << taskId << "finished.";
    }

private:
    int taskId; // 任务标识符
};

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

    QThreadPool pool; // 创建线程池

    // 提交多个任务
    for (int i = 0; i < 3; ++i) {
        MyTask *task = new MyTask(i); // 创建任务实例
        pool.start(task); // 提交到线程池
    }

    pool.waitForDone(); // 等待所有任务完成

    return app.exec();
}

代码解析

  • MyTask 类:该类继承自 QRunnable ,重载 run() 方法,实现了任务的具体逻辑。在 run() 中,它输出当前线程的 ID,并模拟了一个耗时操作。
  • 创建 QThreadPool 实例:在 main() 函数中创建一个 QThreadPool 实例来管理线程。
  • 提交任务:在一个循环中创建多个 MyTask 实例,并通过 pool.start(task) 提交到线程池中。
  • 等待完成:使用 pool.waitForDone() 来阻塞主线程,直到所有任务完成。

运行结果:

Task 1 is running in thread: QThreadPoolThread(0x2cd017f6140, name = "Thread (pooled)")
Task 0 is running in thread: QThreadPoolThread(0x2cd017f6290, name = "Thread (pooled)")
Task 2 is running in thread: QThreadPoolThread(0x2cd017f6380, name = "Thread (pooled)")
Task 2 finished.
Task 0 finished.
Task 1 finished.

QThreadStorage

QThreadStorage 是一个用于管理线程局部存储的类。它允许每个线程拥有独立的数据副本,避免了线程之间的数据竞争,并简化了多线程编程的复杂性。不同线程中的数据不会互相干扰,每个线程可以方便地存取自己的数据。

特点

  • 独立性:每个线程的数据相互独立。
  • 简洁性:不需要手动管理同步,减少复杂性。
  • 性能:适合存储小型数据结构。

常用方法

  • localData() :返回当前线程的数据引用,如果数据不存在则会创建一个新实例。
  • setLocalData() :设置当前线程的数据副本。
  • remove() :移除当前线程的数据副本(可选,通常在不再需要时调用)。

示例:创建多个线程,每个线程存储并打印自己的数据。

#include <QCoreApplication>
#include <QThread>
#include <QThreadStorage>
#include <QDebug>

class WorkerThread : public QThread
{
public:
    void run() override {
        // 确保线程存储正确初始化
        if (!threadStorage.hasLocalData()) {
            threadStorage.setLocalData(0); // 初始化为0或其它合适的值
        }

        int &data = threadStorage.localData();
        data = reinterpret_cast<intptr_t>(QThread::currentThreadId()); // 转换为整数类型

        qDebug() << "Thread ID:" << reinterpret_cast<intptr_t>(QThread::currentThreadId()) << ", Stored Data:" << data;
    }

private:
    static QThreadStorage<int> threadStorage; // 静态数据存储
};

// 定义静态成员
QThreadStorage<int> WorkerThread::threadStorage;

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

    const int numThreads = 5;
    WorkerThread threads[numThreads];

    // 启动线程
    for (int i = 0; i < numThreads; ++i) {
        threads[i].start();
    }

    // 等待所有线程完成
    for (int i = 0; i < numThreads; ++i) {
        threads[i].wait();
    }

    return app.exec();
}

说明

  • WorkerThread 类:继承自 QThread ,重写 run() 方法以定义线程的工作。
  • QThreadStorage:使用静态成员 threadStorage 为每个线程存储一个整数。
  • 数据存储:每个线程都通过 localData() 方法访问和修改自己的数据。

运行结果:

Thread ID: 5160 , Stored Data: 5160
Thread ID: 19012 , Stored Data: 19012
Thread ID: 19980 , Stored Data: 19980
Thread ID: 19324 , Stored Data: 19324
Thread ID: 7404 , Stored Data: 7404

多线程编程框架

Qt的多线程支持,主要通过 QThread 类和信号/槽机制实现。

示例:

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

class Worker : public QObject {
    Q_OBJECT
public slots:
    void doWork() {
        for (int i = 0; i < 5; ++i) {
            qDebug() << "Worker thread:" << QThread::currentThread() << "count:" << i;
            QThread::sleep(1); // 模拟工作
        }
        emit finished();
    }

signals:
    void finished();
};

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

    QThread thread;
    Worker worker;

    // 线程和工作对象之间的连接
    worker.moveToThread(&thread);
    
    QObject::connect(&thread, &QThread::started, &worker, &Worker::doWork);
    QObject::connect(&worker, &Worker::finished, &thread, &QThread::quit);
    QObject::connect(&worker, &Worker::finished, &worker, [&]() {
        qDebug() << "Worker finished.";
        app.quit();
    });
    
    thread.start();

    return app.exec();
}

#include "main.moc"

运行结果:

Worker thread: QThread(0xfcb1fff6a0) count: 0
Worker thread: QThread(0xfcb1fff6a0) count: 1
Worker thread: QThread(0xfcb1fff6a0) count: 2
Worker thread: QThread(0xfcb1fff6a0) count: 3
Worker thread: QThread(0xfcb1fff6a0) count: 4
Worker finished.
QThread: Destroyed while thread is still running

线程同步

在多线程程序中,由于多个线程可能同时访问和修改共享资源,为了保证数据的安全性和程序的正确性,需要进行线程之间的通信和同步。

Qt 框架为此提供了一系列的类来实现线程同步,主要包括:QMutex、QWaitCondition 和 QSemaphore。

线程同步的基本概念

  • 线程通信:不同线程间需要传递信息或状态。
  • 资源共享:多个线程可能同时访问同一资源,需确保资源访问的原子性和安全性。
  • 同步机制:用于协调多个线程的执行顺序,确保它们按预期工作。

Qt 提供的线程同步类

QMutex

  • QMutex 是互斥锁,用于保护共享资源,确保同一时刻只有一个线程可以访问。
  • 提供锁定和解锁机制,防止多个线程同时修改共享数据。

QWaitCondition

  • QWaitCondition 用于实现条件等待,允许线程在特定条件下被唤醒。
  • 与 QMutex 一起使用,允许线程等待某个条件成立后再继续执行。

QSemaphore

  • QSemaphore 是信号量,用于控制对共享资源的访问数量。
  • 用于限制对特定资源的并发访问数量,防止资源耗尽或过度使用。

如何使用这些类进行线程同步

  1. 确定需要保护的共享资源。
  2. 选择合适的同步机制(如 QMutex、QWaitCondition 或 QSemaphore)。
  3. 实施锁定和解锁机制,确保线程安全地访问共享资源。
  4. 使用条件等待(如 QWaitCondition)来处理线程的唤醒和等待。
  5. 测试和调试多线程程序,确保同步机制正确工作。

互斥量、读写锁、条件变量和信号量的使用场景:

同步原件 描述 使用场景 示例
**互斥量 ** 一种基本的锁,用于保证同一时间只有一个线程访问共享资源。 当需要对共享变量进行读写,且读写操作不适合并发时,例如日志系统。 在一个线程中写数据时,使用互斥量锁定,确保没有其他线程可以同时写入。
读写锁 允许多个线程同时读取,但在写入时会阻塞所有读和写操作。 当读操作远多于写操作时,例如缓存系统或数据库的读取。 多个线程需频繁读取配置文件时,可以使用读写锁以提高并发性。
条件变量 用于线程之间的信号传递,允许一个或多个线程等待特定条件的发生。 生产者-消费者模型,线程需要等待某条件成立后再继续执行。 当缓冲区为空时,消费者线程等待,直到生产者线程提供新数据。
信号量 控制同时访问某种资源的线程数量,计数信号量可以允许多个线程访问。 限制访问资源的线程数量,例如限制线程池的并发执行数量。 限制同时处理请求的线程数量,以防过载的网络服务。

互斥量:用于确保打印日志的线程安全。

QMutex mutex;
mutex.lock();
// 进行写操作
mutex.unlock();

读写锁:用于高并发读取配置的情况下。

QReadWriteLock lock;
lock.lockForRead();
// 进行读取操作
lock.unlock();

条件变量:在生产者消费者模型中,消费者线程等待新数据的到来。

QWaitCondition condition;
condition.wait(&mutex); // 等待被唤醒

信号量:控制同时处理连接的线程数量。

QSemaphore semaphore(3); // 最多允许3个线程访问
semaphore.acquire();
// 访问共享资源
semaphore.release();

互斥量

QMutex 和 QMutexLocker 是基于互斥量(mutex)的线程同步类,用于保护共享资源,避免多线程同时访问导致的竞争条件和数据混乱。

QMutex:定义的实例是互斥量,主要用于控制多个线程对共享资源的访问。

主要函数:

方法名 描述
lock() 锁定互斥量,如果互斥量已被其他线程锁定,则当前线程会被阻塞直到互斥量解锁。
unlock() 解锁互斥量,与 lock() 配对使用。
tryLock() 尝试锁定互斥量,如果成功则返回true,否则返回false,不会等待。
tryLock(int timeout) 尝试锁定互斥量,如果互斥量被占用,则最多等待指定的timeout毫秒。

QMutexLocker:是一个自动管理互斥量锁定和解锁的类。其构造函数锁定互斥量,而析构函数则解锁互斥量。使用QMutexLocker可以简化需要手动配对lock()和unlock()的代码逻辑,减少出错的可能性。

QMutex

QMutex 是 Qt 框架中用于线程同步的类,主要用于保护共享资源的访问,确保同一时刻只有一个线程能够访问特定的代码段或数据。它通过互斥量(mutex)实现。

主要功能:

  • lock():锁定互斥量。如果互斥量已被其他线程锁定,则当前线程会被阻塞,直到该互斥量被解锁。
  • unlock():解锁互斥量,必须在成功锁定后调用。
  • tryLock():尝试锁定互斥量,若成功则返回 true ,否则返回 false ,不等待。
  • tryLock(int timeout):尝试在指定的超时时间内锁定互斥量。

示例:多个线程访问一个共享计数器

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

class Counter {
public:
    Counter() : count(0) {}

    void increment() {
        mutex.lock();         // 加锁
        count++;              // 访问共享资源
        qDebug() << "Count:" << count;
        mutex.unlock();       // 解锁
    }

private:
    int count;              // 共享计数器
    QMutex mutex;           // 互斥量
};

class WorkerThread : public QThread {
public:
    WorkerThread(Counter *counter) : counter(counter) {}

protected:
    void run() override {
        for (int i = 0; i < 5; ++i) {
            counter->increment(); // 调用计数器的增量方法
            QThread::sleep(1);    // 模拟工作
        }
    }

private:
    Counter *counter;        // 指向共享计数器的指针
};

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

    Counter counter; // 创建共享计数器

    // 创建多个工作线程
    WorkerThread thread1(&counter);
    WorkerThread thread2(&counter);

    // 启动线程
    thread1.start();
    thread2.start();

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

    return app.exec();
}

代码说明:

  • Counter 类:包含一个共享计数器和一个 QMutex 实例。 increment() 方法负责加锁、增加计数并解锁。
  • WorkerThread 类:继承自 QThread ,在 run() 方法中调用 Counter 的 increment() 方法多次。
  • 主函数:创建 Counter 的实例和工作线程,启动线程并等待其完成。

运行结果:

Count: 1
Count: 2
Count: 3
Count: 4
Count: 5
Count: 6
Count: 7
Count: 8
Count: 9
Count: 10
QMutexLocker

QMutexLocker 是 Qt 框架中用于简化互斥量管理的类。它通过 RAII(资源获取即初始化)机制,自动管理 QMutex 的锁定和解锁,确保互斥量的正确使用。

主要特点:

  • 在构造时自动锁定所传递的互斥量。
  • 在析构时自动解锁互斥量,避免了手动调用 unlock() 的需要。
  • 可以防止因异常或提前返回而导致的死锁问题。

示例:如何使用 QMutexLocker 的简单示例,多个线程访问一个共享计数器。

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

class Counter {
public:
    Counter() : count(0) {}

    void increment() {
        QMutexLocker locker(&mutex); // 自动加锁
        count++;                     // 访问共享资源
        qDebug() << "Count:" << count;
        // 退出函数时自动解锁
    }

private:
    int count;                     // 共享计数器
    QMutex mutex;                  // 互斥量
};

class WorkerThread : public QThread {
public:
    WorkerThread(Counter *counter) : counter(counter) {}

protected:
    void run() override {
        for (int i = 0; i < 5; ++i) {
            counter->increment(); // 调用计数器的增量方法
            QThread::sleep(1);    // 模拟工作
        }
    }

private:
    Counter *counter;           // 指向共享计数器的指针
};

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

    Counter counter; // 创建共享计数器

    // 创建多个工作线程
    WorkerThread thread1(&counter);
    WorkerThread thread2(&counter);

    // 启动线程
    thread1.start();
    thread2.start();

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

    return app.exec();
}

代码说明:

  • Counter 类:包含一个共享计数器和一个 QMutex 实例。 increment() 方法使用 QMutexLocker 来自动管理互斥量的锁定和解锁。
  • WorkerThread 类:继承自 QThread ,在 run() 方法中调用 Counter 的 increment() 方法多次。
  • 主函数:创建 Counter 的实例和工作线程,启动线程并等待其完成。

优势:

  • 使用 QMutexLocker 简化了代码,减少了手动管理互斥量的复杂性。
  • 确保了即使在出现异常或提前返回的情况下,互斥量也会及时解锁,从而避免死锁。

运行结果:

Count: 1
Count: 2
Count: 3
Count: 4
Count: 5
Count: 6
Count: 7
Count: 8
Count: 9
Count: 10

读写锁

互斥量的作用与限制

  • 互斥量用于控制多个线程对共享资源的访问,确保同一时刻只有一个线程能够使用特定资源。
  • 使用互斥量的主要问题是它会使得多个线程在访问资源时形成排队,造成线程间的串行化。

多线程读取变量的场景

  • 在多线程环境中,有时多个线程只是读取某个变量,并不对其进行修改。
  • 在这种情况下,多个线程同时读取变量是安全的,不需要使用互斥量进行同步。

使用互斥量在读取场景下的性能问题

  • 即使只是读取操作,如果使用互斥量进行同步,也会导致线程排队,降低程序的并发性能。
  • 因为互斥量造成的等待和上下文切换会增加额外的开销,这在高并发环境下是不希望的。

使用读写锁优化

  • 在确定多个线程只是读取变量的情况下,可以考虑不使用互斥量,以提高程序的性能。
  • 如果必须同步访问共享资源,可以考虑其他同步机制,如读写锁(允许多个读者同时访问,但只允许一个写者),以更精细地控制资源的访问。
QReadWriteLock

读写锁基本概念

  • 读操作:多个线程可以同时进行读取操作。
  • 写操作:只有一个线程可以写入,且在写操作进行时,所有的读操作会被阻塞。
  • 性能优化:适用于读操作远多于写操作的场景,提高了并发性能。

主要方法

  • lockForRead() :获取读锁,允许多个读线程同时访问。
  • lockForWrite() :获取写锁,阻止其他读或写线程。
  • unlock() :释放锁。

示例:如何用 QReadWriteLock 实现读写操作。

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

class SharedData {
public:
    void addData(int value) {
        writeLock.lockForWrite();
        data.append(value);
        writeLock.unlock();
        qDebug() << "Data added:" << value;
    }

    void readData() {
        readLock.lockForRead();
        qDebug() << "Current data:" << data;
        readLock.unlock();
    }

private:
    QVector<int> data;
    QReadWriteLock readLock;
    QReadWriteLock writeLock;
};

class WriterThread : public QThread {
public:
    WriterThread(SharedData *sharedData) : sharedData(sharedData) {}

protected:
    void run() override {
        for (int i = 0; i < 5; ++i) {
            sharedData->addData(i);
            QThread::sleep(1);
        }
    }

private:
    SharedData *sharedData;
};

class ReaderThread : public QThread {
public:
    ReaderThread(SharedData *sharedData) : sharedData(sharedData) {}

protected:
    void run() override {
        for (int i = 0; i < 5; ++i) {
            sharedData->readData();
            QThread::sleep(1);
        }
    }

private:
    SharedData *sharedData;
};

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

    SharedData sharedData;
    
    WriterThread writer(&sharedData);
    ReaderThread reader(&sharedData);

    writer.start();
    reader.start();

    writer.wait();
    reader.wait();

    return app.exec();
}

代码说明

  1. SharedData 类:该类中包含了一个 QVector 用于存储数据,和一个 QReadWriteLock 用于控制对数据的访问。
    • addData(int value) 方法用于写入数据,并使用写锁包裹。
    • readData() 方法用于读取数据,并使用读锁包裹。
  2. WriterThread 类:继承自 QThread ,模拟数据写入,调用 addData() 方法。
  3. ReaderThread 类:继承自 QThread ,模拟数据读取,调用 readData() 方法。
  4. 主函数:创建共享数据对象和线程对象,启动它们并等待完成。

运行结果:

Current data: QList(0)
Data added: 0
Data added: 1
Current data: QList(0, 1)
Data added: 2
Current data: QList(0, 1, 2)
Data added: 3
Current data: QList(0, 1, 2, 3)
Data added: 4
Current data: QList(0, 1, 2, 3, 4)

条件等待

概述

在多线程程序中,线程同步是确保线程间协调工作的关键。QWaitCondition是Qt提供的一种改进的线程同步方法,主要用于在特定条件下通知其他线程,以实现更高效的多线程协作。

  • 用于解决多线程环境中的条件等待问题。

QWaitCondition原理

QWaitCondition是基于条件变量的一种实现,允许一个线程(通常是等待数据的线程)在等待某个条件成立时进入休眠状态,当另一个线程(通常是完成某项工作的线程)通过该条件通知等待的线程时,等待的线程将被唤醒并继续执行。这种机制提高了多线程间的响应速度和效率。

QWaitCondition功能

QWaitCondition的主要功能包括:

  • 与QMutex或QReadWriteLock结合使用:确保在修改共享资源时的线程安全。
  • 条件等待:允许一个线程在满足特定条件时进入等待状态,直到其他线程通过通知来唤醒它。
  • 通知机制:一个线程可以通过QWaitCondition通知一个或多个等待的线程,让它们知道条件已经成立,可以进行下一步操作。
QWaitCondition

QWaitCondition 通常与 QMutex 或 QReadWriteLock 配合使用,这样线程可以在特定条件下挂起并等待其他线程的通知。

主要功能

  • 条件等待:允许一个线程在某个条件不满足时挂起,等待其他线程通知它继续执行。
  • 高效同步:相较于传统的锁机制, QWaitCondition 可以避免忙等待,提高多线程程序的响应速度和效率。
  • 通知机制:一个线程可以通过 wakeOne() 或 wakeAll() 方法来通知一个或多个等待的线程,从而允许它们恢复执行。

基本用法

在使用 QWaitCondition 时,通常遵循以下步骤:

  1. 创建互斥量:使用 QMutex 来保护共享资源。
  2. 创建条件变量:使用 QWaitCondition 进行条件等待和通知。
  3. 加锁并等待:线程在检查条件未满足时加锁并调用 wait() ,它会自旋并释放锁,直到被通知。
  4. 修改共享资源:在某个线程中,修改共享资源后,通过 wakeOne() 或 wakeAll() 通知等待的线程。

示例:如何使用 QWaitCondition 进行线程同步

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

// 定义一个生产者类,继承自QThread。
class Producer : public QThread {
public:
    Producer(QWaitCondition &condition, QMutex &mutex, bool &dataReady)
        : condition(condition), mutex(mutex), dataReady(dataReady) {}

    void run() override {
        QThread::sleep(1); // 模拟工作
        mutex.lock(); // 加锁,保护共享资源。
        dataReady = true; // 设置数据准备好的标志。
        condition.wakeOne(); // 通知消费者
        mutex.unlock(); // 解锁,允许其他线程访问。
        qDebug() << "Producer: Data is ready.";
    }

private:
    QWaitCondition &condition; // 条件变量引用,用于线程同步。
    QMutex &mutex; // 互斥锁引用,用于保护共享资源。
    bool &dataReady; // 布尔标志引用,表示数据是否已经准备好。
};

// 定义一个消费者类,继承自QThread。
class Consumer : public QThread {
public:
    Consumer(QWaitCondition &condition, QMutex &mutex, bool &dataReady)
        : condition(condition), mutex(mutex), dataReady(dataReady) {}

    void run() override {
        mutex.lock();
        while (!dataReady) {
            condition.wait(&mutex); // 等待数据准备好
        }
        mutex.unlock();
        qDebug() << "Consumer: Data has been consumed.";
    }

private:
    QWaitCondition &condition;
    QMutex &mutex;
    bool &dataReady;
};

int main(int argc, char *argv[]) {
    QCoreApplication app(argc, argv);
    
    QWaitCondition condition; // 创建条件变量实例。
    QMutex mutex; // 创建互斥锁实例
    bool dataReady = false; // 创建并初始化一个表示数据是否准备好的布尔标志。

    Producer producer(condition, mutex, dataReady); // 创建生产者实例并初始化。
    Consumer consumer(condition, mutex, dataReady); // 创建消费者实例并初始化。
    
    consumer.start(); // 启动消费者线程。
    producer.start(); // 启动生产者线程。

    producer.wait(); // 等待生产者线程结束。
    consumer.wait(); // 等待消费者线程结束。

    return app.exec();
}

说明

  • Producer 线程:模拟生产过程,准备好数据后调用 wakeOne() 通知消费者。
  • Consumer 线程:在数据未准备好的情况下调用 wait() 进入等待状态,直到接收到通知。
  • 互斥量: QMutex 用来保护共享变量,确保线程安全。

运行结果:

Consumer: Data has been consumed.
Producer: Data is ready.

信号量

QSemaphore

在 Qt 中,信号量通常是通过 QSemaphore 类实现的。信号量是一种同步工具,用于控制对共享资源的访问,尤其在多线程编程中。

基本功能

  • 信号量维护一个计数器,表示可用资源的数量。
  • 当线程请求资源时,如果可用资源大于 0,计数器减一,线程继续执行;否则,线程会被阻塞,直到资源可用。

可初始化

  • 支持初始化信号量的计数值,指定初始可用资源的数量。

简化线程间同步

  • 通过控制对共享资源的访问,避免数据竞争和不正确的状态。

常用方法

  • 构造函数

    QSemaphore semaphore(int initialCount);
    
  • 获取资源

    semaphore.acquire(); // 请求资源
    
  • 释放资源

    semaphore.release(); // 释放资源
    

示例:如何使用 QSemaphore 在多个线程之间控制对共享资源的访问。

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

constexpr int NUM_THREADS = 5; // 线程数量
QSemaphore semaphore(2); // 初始化信号量,允许 2 个线程同时访问资源

class Worker : public QThread {
public:
    void run() override {
        // 请求资源
        semaphore.acquire();
        qDebug() << "Thread" << QThread::currentThread()->objectName() << "is using the resource";
        
        // 模拟工作
        QThread::sleep(1);
        
        // 释放资源
        qDebug() << "Thread" << QThread::currentThread()->objectName() << "is releasing the resource";
        semaphore.release();
    }
};

int main(int argc, char *argv[]) {
    QCoreApplication a(argc, argv);
    
    Worker threads[NUM_THREADS];
    for (int i = 0; i < NUM_THREADS; ++i) {
        threads[i].setObjectName(QString::number(i));
        threads[i].start();
    }

    for (int i = 0; i < NUM_THREADS; ++i) {
        threads[i].wait(); // 等待所有线程结束
    }

    return a.exec();
}

代码解析:

信号量初始化

QSemaphore semaphore(2); // 允许 2 个线程同时访问

工作线程

每个 Worker 类代表一个线程,在其 run 方法中:

  • 请求资源: semaphore.acquire();
  • 模拟工作(休眠 1 秒)。
  • 释放资源: semaphore.release();

主函数

  • 创建并启动多个 Worker 线程,等待所有线程完成工作。

运行结果:

Thread "0" is using the resource
Thread "2" is using the resource
Thread "2" is releasing the resource
Thread "1" is using the resource
Thread "0" is releasing the resource
Thread "4" is using the resource
Thread "4" is releasing the resource
Thread "3" is using the resource
Thread "1" is releasing the resource
Thread "3" is releasing the resource
Logo

有“AI”的1024 = 2048,欢迎大家加入2048 AI社区

更多推荐