首页 > 其他分享 >Qt - 多线程之线程同步

Qt - 多线程之线程同步

时间:2024-11-20 14:59:29浏览次数:1  
标签:多线程 Qt 获取 QMutex 互斥 线程 mutex include

一、线程为什么要同步

使用两个线程对一个全局变量做累加,从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

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

标签:多线程,Qt,获取,QMutex,互斥,线程,mutex,include
From: https://www.cnblogs.com/zhuchunlin/p/18557344

相关文章

  • Qt - 多线程之线程的开始、暂停、恢复、停止
    示例1在Qt中,可以使用QThread的线程控制功能来暂停和恢复一个线程。这里是一个简单的例子: #include<QThread>#include<QDebug>classWorkerThread:publicQThread{voidrun()override{qDebug()<<"Threadisrunning";//执行一些任务......
  • QT5.15.2 连接MySQL 驱动问题解决方案,无论菜鸟️还是老鸟,解决了就是好鸟
    最近在学QT,现在QT只能在线安装了,用了几天,看到数据库时,需要用MySQL,结果出现了问题。QSqlDatabase:QMYSQLdrivernotloaded、QSqlDatabase:availabledrivers:QSQLITEQODBCQODBC3QPSQLQPSQL7、Sqlconnectfailed、"DrivernotloadedDrivernotloaded"网上找到很多......
  • Qt 重写paintEvent事件划线
    可以自定义一个类QtImageLabel继承于QLabel,重写paintEvent事件划线,写文字等。如果用ui设计,将QLabel控件提升为QtImageLabel类型即可。QtImageLabel.hprotected:voidpaintEvent(QPaintEvent*)override;QtImageLabel.cpp#pragmaexecution_character_set("utf-8......
  • 模拟线程池与异步方法调用查询接口优化
    问题:批量查询如何优化?entity实体类packagecom.itheima.alipay.prop;importlombok.Data;@DatapublicclassUserInfo{privateLonguserId;privateStringusername;privateintage;publicUserInfo(LonguserId,Stringusername,intage){......
  • QT 实现表格展示第一列为CheckBox(复选框)
    1.界面实现效果在Qt中,如果你想要在QTableView中实现复选框展示数据,示例:实现第一列为复选框(checkBox),需要自定义的QAbstractTableModel,重写data和setData方法来返回和设置复选框的状态,并且还需要重写flags方法来允许复选框被点击。2.自定义CustomModel需要实现以下几个方......
  • Java面试之多线程&并发篇(6)
    前言本来想着给自己放松一下,刷刷博客,突然被几道面试题难倒!产生死锁的四个必要条件?如何避免死锁?线程池核心线程数怎么设置呢?Java线程池中队列常用类型有哪些?似乎有点模糊了,那就大概看一下面试题吧。好记性不如烂键盘***12万字的java面试题整理******java核心面试知识整理***......
  • C#单线程环境下实现每秒百万级调度
    C#并发控制框架:单线程环境下实现每秒百万级调度 阅读目录前言并发流程控制框架框架优势框架示例框架地址总结最后前言在工业自动化和机器视觉领域,对实时性、可靠性和效率的要求越来越高。为了满足这些需求,我们开发了一款专为工业自动化运动控制和机器视觉流程开发......
  • Qt Label 显示图片
    一般这样子://跟随比例变化ui->label->setScaledContents(true);QPixmappixmap("./01.jpg");//pixmap.load("./01.jpg");//让图片大小适应控件大小,如果不需要,可以直接显示原图QPixmaps_img=pixmap.scaled(ui->label->size(),Qt::KeepAspectRatio,Qt::SmoothTran......
  • Linux线程退出、资源回收、资源清理的方法
    参考 Linux线程退出、资源回收、资源清理的方法_linux线程退出会释放哪些资源-CSDN博客 首先说明线程中要回收哪些资源,理解清楚了这点之后在思考资源回收的问题。1、子线程创建时从父线程copy出来的栈内存;线程退出有多种方式,如return,pthread_exit,pthread_cancel等;线......
  • 第 1 章 并发编程线程基础
    目录1.1什么是线程 1.2线程创建与运行 1、继承Thread类方式的实现。2、实现Runnable接口的run方法3、使用FutureTask方式1.3线程通知与等待1.wait()函数2.wait(longtimeout)函数3.wait(longtimeout,intnanos)函数4.notify()函数5.notifyAll()......