Qt - 多线程之线程同步

一、线程为什么要同步

使用两个线程对一个全局变量做累加,从0加到10,所以只要每个线程累加到5就行。代码如下所示:

#include <QApplication>
#include <QThread>
#include <QDebug>

// 定义共享资源
int sharedValue = 0;

// 定义一个线程类
class MyThread : public QThread
{
public:
    void run() override
    {
        for(int i = 0; i < 5; i++)
        {
            sharedValue++; // 访问共享资源
            qDebug() << "Thread ID: " << QThread::currentThreadId() << " - Shared Value: " << sharedValue;
            msleep(1000); // 线程休眠1秒
        }
    }
};

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

    MyThread thread1;
    MyThread thread2;

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

    thread1.wait();
    thread2.wait();

    qDebug() << "Final Shared Value: " << sharedValue;

    return a.exec();
}

运行结果:

可以看到两个线程不是交替着执行的,会重复执行,所以并不是我们想要的结果。一般这种问题需要采用线程同步的方式。

结果分析: 明显看出在未加锁情况下对临界资源的访问出现混乱的结果。

 

二、线程同步方式

1、QMutex(互斥锁)

互斥锁用于保护共享资源,确保在同一时间只有一个线程能够访问该资源。线程在访问共享资源之前需要获取互斥锁,使用完后再释放互斥锁,以确保同一时间只有一个线程在执行关键代码段。

 

QMutex的使用步骤如下:

1.创建QMutex对象:在需要进行线程同步的地方,首先创建一个QMutex对象。

 
QMutex mutex;

2.获取互斥锁:在访问共享资源之前,线程需要获取互斥锁。使用lock()方法获取互斥锁。如果互斥锁已被其他线程占用,当前线程会被阻塞,直到互斥锁被释放。

 
mutex.lock();

3.访问共享资源:获取到互斥锁后,线程可以安全地访问共享资源。

 
// 访问共享资源的代码

4.释放互斥锁:在线程完成对共享资源的访问之后,需要释放互斥锁,以便其他线程可以获取到互斥锁进行访问

 
mutex.unlock();

示例代码:

 
#include <QApplication>
#include <QThread>
#include <QDebug>
#include <QMutex>

// 定义共享资源
int sharedValue = 0;
// 定义互斥锁
QMutex mutex;

// 定义一个线程类
class MyThread : public QThread
{
public:
    void run() override
    {
        for(int i = 0; i < 5; i++)
        {
            mutex.lock(); // 加锁
            sharedValue++; // 访问共享资源
            qDebug() << "Thread ID: " << QThread::currentThreadId() << " - Shared Value: " << sharedValue;
            msleep(1000); // 线程休眠1秒
            mutex.unlock(); // 解锁
        }
    }
};

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

    MyThread thread1;
    MyThread thread2;

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

    thread1.wait();
    thread2.wait();

    qDebug() << "Final Shared Value: " << sharedValue;

    return a.exec();
}

运行效果:

 

2、QReadWriteLock(读写锁)

 读写锁是Qt中用于实现读写线程同步的一种机制。它提供了一种更高效的方式来管理对共享资源的读写操作,允许多个线程同时进行读操作,但只允许一个线程进行写操作。

 

QReadWriteLock的使用步骤如下:

1.创建QReadWriteLock对象:在需要进行读写线程同步的地方,首先创建一个QReadWriteLock对象。

 
QReadWriteLock rwLock;

2.获取读锁:当线程需要进行读操作时,使用lockForRead()方法获取读锁。多个线程可以同时获取读锁,以进行并发的读操作。

 
rwLock.lockForRead();

3.进行读操作:获取到读锁后,线程可以安全地进行读操作,访问共享资源

 
// 进行读操作的代码

4.释放读锁:在读操作完成后,使用unlock()方法释放读锁,允许其他线程获取读锁。

 
rwLock.unlock();

5.获取写锁:当线程需要进行写操作时,使用lockForWrite()方法获取写锁。写锁是独占的,只允许一个线程获取写锁进行写操作,其他线程需要等待写锁的释放。

 
rwLock.lockForWrite();

6.进行写操作:获取到写锁后,线程可以安全地进行写操作,修改共享资源。

 
// 进行写操作的代码

7.释放写锁:在写操作完成后,使用unlock()方法释放写锁,允许其他线程获取读锁或写锁。

 
rwLock.unlock();

示例代码:

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

QString sharedData; // 共享数据变量

QReadWriteLock rwLock; // 读写锁

// 读取操作线程
class ReaderThread : public QThread
{
public:
    void run() override
    {
        rwLock.lockForRead(); // 以读取方式加锁
        qDebug() << "Read Data: " << sharedData; // 输出读取的数据
        rwLock.unlock(); // 释放锁
    }
};

// 写入操作线程
class WriterThread : public QThread
{
public:
    void run() override
    {
        rwLock.lockForWrite(); // 以写入方式加锁
        sharedData = "Hello, world!"; // 写入数据
        qDebug() << "Write Data: " << sharedData; // 输出写入的数据
        rwLock.unlock(); // 释放锁
    }
};

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

    // 创建读取线程和写入线程
    WriterThread writer;
    ReaderThread reader;

    // 启动线程
    writer.start(); // 启动写入线程
    reader.start(); // 启动读取线程

    // 等待线程结束
    writer.wait(); // 等待写入线程结束
    reader.wait(); // 等待读取线程结束

    return a.exec();
}

执行结果:

 

3、QWaitCondition(等待条件)

 QWaitCondition是Qt中用于线程同步的一种机制,它允许线程等待特定条件的发生,并在条件满足时被唤醒继续执行。QWaitCondition通常与QMutex一起使用,以提供更复杂的线程同步机制。

QWaitCondition的使用步骤如下:

1.创建QWaitCondition对象:在需要进行线程同步的地方,首先创建一个QWaitCondition对象。

 
QWaitCondition condition;

2.创建QMutex对象:为了保护条件的读写操作,创建一个QMutex对象。

 
QMutex mutex;

3.在等待条件的线程中等待:在线程需要等待特定条件的发生时,使用wait()方法使线程进入等待状态。

 
mutex.lock();
condition.wait(&mutex);
mutex.unlock();

在调用wait()方法之前,需要先获取到互斥锁(QMutex)。这样可以确保线程在等待之前能够安全地访问和检查条件。

4.在其他线程中满足条件并唤醒等待线程:当某个条件满足时,通过wakeOne()wakeAll()方法唤醒等待的线程。

 
mutex.lock();
condition.wakeOne(); // 或者使用 condition.wakeAll();
mutex.unlock();

示例代码:

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

QMutex mutex;  // 创建一个互斥锁,确保线程安全
QWaitCondition queueNotEmpty; // 创建一个条件变量,表示队列非空
QQueue<int> queue; // 创建一个队列用于存储数据

// 生产者线程
class ProducerThread : public QThread
{
public:
    void run() override
    {
        for (int i = 0; i < 10; ++i) {
            // 生产数据并加入队列
            {
                QMutexLocker locker(&mutex); // 加锁
                queue.enqueue(i); // 生产数据并加入队列
                qDebug() << "Produced: " << i;
                queueNotEmpty.wakeOne();  // 通知消费者队列非空
            }
            msleep(100);  // 休眠一段时间
        }
    }
};

// 消费者线程
class ConsumerThread : public QThread
{
public:
    void run() override
    {
        for (int i = 0; i < 10; ++i) {
            // 检查队列是否为空,如果为空则等待
            {
                QMutexLocker locker(&mutex); // 加锁
                while (queue.isEmpty()) {
                    queueNotEmpty.wait(&mutex); // 等待条件变量,直到队列非空
                }
                int value = queue.dequeue();  // 从队列中取出数据
                qDebug() << "Consumed: " << value;
            }
            msleep(200);  // 休眠一段时间
        }
    }
};

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

    // 创建生产者线程和消费者线程
    ProducerThread producer;
    ConsumerThread consumer;

    // 启动线程
    producer.start();
    consumer.start();

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

    return a.exec();
}

执行结果:

 

4、QSemaphore(信号量)

QSemaphore 是 Qt 中用于实现信号量的类,用于控制对共享资源的访问数量。它可以用来限制同时访问某一资源的线程数量,也可以用于线程之间的同步。QSemaphore 可以被获取和释放,当信号量的值为正时,线程可以获得该信号量;当信号量的值为零时,线程将被阻塞,直到有线程释放信号量。通过获取和释放信号量,可以实现线程之间的协调和资源的管理。

示例代码:

 
#include <QApplication>
#include <QThread>
#include <QDebug>
#include <QSemaphore>

QSemaphore semaphore(2); // 定义能够同时访问资源的线程数量为2的信号量

class MyThread : public QThread // 定义一个线程类
{
public:
    void run() override
    {
        if(semaphore.tryAcquire())
        { // 尝试获取信号量
            qDebug() << "Thread ID: " << QThread::currentThreadId() << " - Acquired Semaphore"; // 输出线程ID和已获取信号量消息
            sleep(2); // 线程休眠2秒
            qDebug() << "Thread ID: " << QThread::currentThreadId() << " - Releasing Semaphore"; // 输出线程ID和释放信号量消息
            semaphore.release(); // 释放信号量
        }
        else
        {
            qDebug() << "Thread ID: " << QThread::currentThreadId() << " - Semaphore not acquired"; // 输出线程ID和未获取信号量消息
        }
    }
};

int main(int argc, char *argv[]) // 主函数
{
    QApplication a(argc, argv); // 创建应用程序对象

    MyThread thread1; // 创建线程对象1
    MyThread thread2; // 创建线程对象2
    MyThread thread3; // 创建线程对象3

    thread1.start(); // 启动线程1
    thread2.start(); // 启动线程2
    thread3.start(); // 启动线程3

    thread1.wait(); // 等待线程1结束
    thread2.wait(); // 等待线程2结束
    thread3.wait(); // 等待线程3结束

    return a.exec(); // 执行应用程序事件循环
}

执行结果:

当QSemaphore semaphore(2):

当QSemaphore semaphore(3):

 

================================================

参考文档:https://blog.csdn.net/qq_45790916/article/details/136390241

参考文档:https://www.cnblogs.com/TechNomad/p/17439960.html

================================================

posted @ 2024-11-20 14:59  [BORUTO]  阅读(4)  评论(0编辑  收藏  举报