Qt/C++面试【速通笔记六】—Qt 中的线程同步

发布于:2025-05-02 ⋅ 阅读:(150) ⋅ 点赞:(0)

在多线程编程中,多个线程同时访问共享资源时,可能会出现数据不一致或者错误的情况。这时,我们需要线程同步机制来保证程序的正确性。Qt 提供了多种线程同步方式,每种方式适用于不同的场景。


1. 互斥锁(QMutex)

QMutex 是最常用的线程同步机制之一,它用于保护共享资源,确保同一时刻只有一个线程可以访问资源。互斥锁(Mutex)也叫排他锁,是一种独占的锁。

代码示例:
#include <QMutex>
#include <QThread>
#include <QDebug>

QMutex mutex;  // 声明一个互斥锁
int sharedData = 0;  // 共享数据

// 安全地递增共享数据
void safeIncrement() {
    mutex.lock();  // 加锁,确保只有一个线程能访问共享数据
    sharedData++;  // 对共享数据进行操作
    mutex.unlock();  // 解锁,允许其他线程访问
}

class MyThread : public QThread {
public:
    void run() override {
        for (int i = 0; i < 1000; ++i) {
            safeIncrement();  // 调用安全递增函数
        }
    }
};

int main() {
    MyThread thread1, thread2;  // 创建两个线程
    thread1.start();  // 启动线程1
    thread2.start();  // 启动线程2
    thread1.wait();  // 等待线程1结束
    thread2.wait();  // 等待线程2结束
    qDebug() << "最终共享数据的值:" << sharedData;  // 输出共享数据的最终值
}

注释:

  • 在多线程环境下,sharedData 是多个线程共享的资源。我们通过 QMutex 进行加锁和解锁操作,确保同一时刻只有一个线程可以访问和修改 sharedData

2. 读写锁(QReadWriteLock)

QReadWriteLock 是一种更细粒度的锁机制。它允许多个线程同时读取数据,但在写数据时,必须独占锁。这样,在读取时多个线程可以并发执行,但写入时会阻塞其他线程的读取和写入。

代码示例:
#include <QReadWriteLock>
#include <QThread>
#include <QDebug>

QReadWriteLock lock;  // 声明读写锁
int sharedData = 0;  // 共享数据

// 读取共享数据
void readData() {
    lock.lockForRead();  // 加读锁,允许多个线程并发读取
    qDebug() << "读取共享数据:" << sharedData;
    lock.unlock();  // 解锁
}

// 写入共享数据
void writeData(int value) {
    lock.lockForWrite();  // 加写锁,独占锁
    sharedData = value;
    qDebug() << "写入共享数据:" << sharedData;
    lock.unlock();  // 解锁
}

class MyThread : public QThread {
public:
    void run() override {
        for (int i = 0; i < 5; ++i) {
            readData();  // 读取共享数据
            writeData(i);  // 写入共享数据
        }
    }
};

int main() {
    MyThread thread1, thread2;  // 创建两个线程
    thread1.start();  // 启动线程1
    thread2.start();  // 启动线程2
    thread1.wait();  // 等待线程1结束
    thread2.wait();  // 等待线程2结束
}

注释:

  • QReadWriteLock 的好处是允许多个线程同时读取数据,但写操作时会阻塞其他线程的读取和写入。适合数据不经常改变但需要频繁读取的场景。

3. 事件和信号槽机制(QEvent, QSignalEmitter)

Qt 的信号和槽机制是非常强大的一种线程间通信方式。它通过事件机制,让一个线程发出信号,另一个线程接收并响应这个信号,避免了显式的锁操作。信号槽机制通过 Qt 自己的事件循环来实现线程安全的通信。

代码示例:
worker.h — Worker 类定义
#ifndef WORKER_H
#define WORKER_H

#include <QObject>
#include <QThread>
#include <QDebug>

class Worker : public QObject {
    Q_OBJECT
public:
    explicit Worker(QObject *parent = nullptr) : QObject(parent) {}

    void doWork() {
        qDebug() << "工作线程中执行:" << QThread::currentThread();
    }
};

#endif // WORKER_H

#### **`workerthread.h` — WorkerThread 类定义**

```cpp
#ifndef WORKERTHREAD_H
#define WORKERTHREAD_H

#include <QThread>
#include <QObject>
#include "worker.h"

class WorkerThread : public QThread {
    Q_OBJECT
public:
    explicit WorkerThread(Worker *worker, QObject *parent = nullptr) 
        : QThread(parent), m_worker(worker) {}

protected:
    void run() override {
        emit workSignal();
    }

signals:
    void workSignal();

private:
    Worker *m_worker;
};

#endif // WORKERTHREAD_H
main.cpp — 主函数
#include <QCoreApplication>
#include <QThread>
#include <QDebug>
#include "worker.h"
#include "workerthread.h"

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

    Worker worker;  // 创建工作对象
    WorkerThread thread(&worker);  // 创建工作线程

    // 连接信号和槽
    QObject::connect(&thread, &WorkerThread::workSignal, &worker, &Worker::doWork);

    thread.start();  // 启动线程
    thread.wait();   // 等待线程结束

    return a.exec();
}

4. 条件变量(QWaitCondition)

QWaitCondition 允许线程在等待某个条件满足时释放锁,并在条件满足时唤醒其他线程。通常与 QMutex 一起使用,用于在多线程间传递信号和等待条件。

代码示例:
#include <QWaitCondition>
#include <QMutex>
#include <QThread>
#include <QDebug>

QMutex mutex;  // 声明一个互斥锁
QWaitCondition condition;  // 声明一个条件变量
bool ready = false;  // 共享条件变量

// 等待条件
void waitForCondition() {
    mutex.lock();
    while (!ready) {  // 如果条件不满足,线程会阻塞
        condition.wait(&mutex);  // 等待
    }
    qDebug() << "条件满足,线程继续执行!";
    mutex.unlock();
}

// 通知条件满足
void notifyCondition() {
    mutex.lock();
    ready = true;  // 改变条件状态
    condition.wakeOne();  // 唤醒一个等待的线程
    mutex.unlock();
}

int main() {
    QThread* thread1 = new QThread();
    QThread* thread2 = new QThread();

    QObject::connect(thread1, &QThread::started, waitForCondition);  // 连接信号和槽
    QObject::connect(thread2, &QThread::started, notifyCondition);  // 连接信号和槽

    thread1->start();  // 启动线程1
    thread2->start();  // 启动线程2

    thread1->wait();  // 等待线程1结束
    thread2->wait();  // 等待线程2结束
}

注释:

  • QWaitCondition 适用于需要线程等待某个条件满足后再继续执行的情况。thread1 会等待 ready 变量为真,然后继续执行,而 thread2 会改变条件并唤醒 thread1

5. 原子操作(QAtomicInt 和 QAtomicPointer)

QAtomicIntQAtomicPointer 提供了线程安全的原子操作,不需要加锁即可安全地操作共享变量。适用于计数器等简单的数值操作。

代码示例:
#include <QAtomicInt>
#include <QThread>
#include <QDebug>

QAtomicInt counter(0);  // 声明原子计数器

// 原子递增计数器
void incrementCounter() {
    counter.fetchAndAddOrdered(1);  // 原子递增操作
}

class MyThread : public QThread {
public:
    void run() override {
        for (int i = 0; i < 1000; ++i) {
            incrementCounter();  // 调用原子递增
        }
    }
};

int main() {
    MyThread thread1, thread2;  // 创建两个线程
    thread1.start();  // 启动线程1
    thread2.start();  // 启动线程2
    thread1.wait();  // 等待线程1结束
    thread2.wait();  // 等待线程2结束
    qDebug() << "最终计数器的值:" << counter;  // 输出计数器的最终值
}

注释:

  • QAtomicInt 允许我们进行线程安全的计数操作,无需加锁,适用于高效的原子操作。

线程同步方法对比

同步方法 优点 缺点 最适用场景
QMutex 简单、直观,适合保护共享资源 每次访问资源都需要加锁,可能会影响性能 需要保证共享资源被多个线程安全访问的场景
QReadWriteLock 允许多个线程并发读取,适用于读多写少的情况 写入时需要独占锁,可能会导致读取阻塞 数据经常被读取,但更新不频繁的场景
信号与槽 实现跨线程通信,不需要显式的锁 适用于信号发出的线程和接收的线程需要有明确的关联 需要线程间通信,且希望简化锁操作的场景
QWaitCondition 允许线程等待条件满足时阻塞和唤醒其他线程 比较适合于等待某个条件的场景,使用复杂 线程需要等待特定条件发生的场景
原子操作 高效、简单,避免了使用锁的复杂性 只适用于简单的数值或指针操作 高性能计数器、指针操作等简单数值操作的场景

网站公告

今日签到

点亮在社区的每一天
去签到