QThread线程同步——加锁

QThread线程同步

在多线程编程中,需要保证线程之间的同步问题,避免运行出现不可预期结果。以下是 QThread 线程同步的几种常见方法:

  1. 互斥锁:使用 QMutex 或 QReadWriteLock 等互斥量,对访问共享资源的代码进行加锁。

  2. 条件变量:使用 QWaitCondition 对象实现等待唤醒机制,使线程根据某个条件休眠,并在满足条件时唤醒线程。

  3. 信号槽机制:使用 QObject::connect() 函数建立信号槽连接,对于主线程和子线程之间的通信可使用 Qt 的信号槽机制。从子线程通过发射信号通知主线程进行操作。

  4. QtConcurrent 模块:使用 QtConcurrent::run() 函数将任务提交给新的线程,然后使用 QFutureWatcher 监控该任务执行状态,以便在异步操作完成时获得通知。

  5. QSemaphore :使用 QSemaphore 实现线程间的信号量机制。

需要注意的是,在多线程环境下,如果应用程序访问和修改共享存储区时没有合适的方法来同步访问,可能导致数据一致性问题或者崩溃。所以在线程间共享数据时,请注意考虑线程同步问题。

互斥锁

互斥锁是一种简单的线程同步方法。QReadWriteLocker结合QReadWriteLock, QMutexLocker结合 QMutex ,是 Qt 框架下常用的互斥锁方法,用于实现多线程之间的同步。两者都可以用于保护临界区资源,使得多个线程能够安全地访问和修改共享数据。

QReadWriteLocker

QReadWriteLocker 是一种读写锁,其设计思想基于读多写少的原则。它提供了读和写两种加锁方式,通过加读锁(QReadLocker)可使得多个线程同时对共享资源进行读操作,而在某个线程需要进行写操作时,则必须首先获得写锁(QWriteLocker),以独占式的方式控制资源的访问。

QMutexLocker

相比较而言,QMutexLocker 是一种互斥锁,它适合于那些需要在特定时刻只有一个线程能够访问临界区资源的情况。在执行被锁保护的代码块之前,程序需要获取一个独占锁,完成操作后释放锁,以便其他线程也能执行操作。

二者的选择

一般来说,在需要保护一个共享资源时,如果使用频繁读取、偶尔写入的场景,应该优先选择QReadWriteLocker,这样可以提高并发度,避免由于争用过程锁导致性能损失。如果是频繁写入或者对共享资源的访问时间较长的情况,应该使用QMutexLocker。

需要注意的是,在实际开发过程中,选择锁的类型除了要根据访问情况之外,还要综合考虑线程个数、共享资源大小和复杂度等多个因素。

QMutexLocker代码示例

#ifndef COUNTERTHREAD_H
#define COUNTERTHREAD_H
#include <QThread>
#include <QMutex>
#include <QMutexLocker>

class Counter : public QObject {
    Q_OBJECT
public:
    Counter() : m_counter(0) {}

    void increment() {
        QMutexLocker locker(&m_lock);
        ++m_counter;
    }

    int value() const {
        QMutexLocker locker(&m_lock);
        return m_counter;
    }

private:
    mutable QMutex m_lock;
    int m_counter;
};

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

    void run() override {
        for (int i = 0; i < 100000; ++i) {
            m_counter->increment();
        }
    }

private:
    Counter *m_counter;
};
#endif // COUNTERTHREAD_H

在上述示例中,我们创建了一个名为 Counter 的类,该类包含一个整型成员变量和用于对其进行自增操作和获取值的成员函数 increment() 和 value()。在这些函数中,我们都使用 QMutexLocker 对 QMutex 实例进行加锁和解锁操作,确保只有一个线程可以访问 m_counter 这个共享变量。

#include <QCoreApplication>
#include <QDebug>
#include "counterthread.h"

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

    Counter counter;

    QList<WorkerThread*> threads;
    // 启动多个线程对计数器进行操作
    for (int i = 0; i < 10; ++i) {
        WorkerThread *thread = new WorkerThread(&counter);
        threads.append(thread);
        thread->start();
    }

    // 等待所有线程执行完成
    for (auto thread: threads) {
        thread->wait();
        delete thread;
    }

    qDebug() << "counter value is" << counter.value();


    return a.exec();
}

在 main() 函数中,我们创建了多个 WorkerThread 的实例,用于并行地对 Counter 进行自增操作,等待所有线程执行完成后输出计数器的最终值。

在没有使用锁的情况下,当多个线程同时访问 Counter 实例并试图修改其内部状态时,可能会发生竞争条件问题,从而导致程序崩溃或者结果异常。通过加锁来限制多个线程对相同资源的访问,确保每次只有一个线程能够成功访问目标资源,提高程序稳定性和正确性。

需要注意的是,在实际应用场景中,为了防止死锁等问题,还要谨慎分配锁的粒度,减少锁的持有时间,以便提高并发度,提升程序的整体性能。

QReadWriteLocker代码示例

#ifndef OBJECTTHREAD_H
#define OBJECTTHREAD_H

#include <QThread>
#include <QReadWriteLock>

class MyObject : public QObject
{
    Q_OBJECT

public:
    MyObject() { m_value = 0; }

    void incrementValue() {
        QWriteLocker locker(&m_lock);
        m_value += 1;
        emit valueChanged(m_value);
    }

signals:
    void valueChanged(int newValue);

public slots:
    void getValue() {
        QReadLocker locker(&m_lock);
        emit valueChanged(m_value);
    }

private:
    int m_value;
    QReadWriteLock m_lock;
};

class WorkerThread : public QThread
{
    Q_OBJECT

public:
    WorkerThread(MyObject* object, bool incrementMode)
        : m_object(object), m_incrementMode(incrementMode) {}

protected:
    void run() Q_DECL_OVERRIDE {
        if (m_incrementMode) {
            // 加 1 操作
            for (int i = 0; i < 1000000; ++i) {
                m_object->incrementValue();
                QThread::msleep(1);
            }
        }
        else {
            // 获取值操作
            for (int i = 0; i < 1000000; ++i) {
                m_object->getValue();
                QThread::msleep(1);
            }
        }
    }

private:
    MyObject* m_object;
    bool m_incrementMode; // True 表示加 1 操作,False 表示获取值操作
};


#endif // OBJECTTHREAD_H

在上述代码中,我们创建了一个名为 MyObject 的类,该类包含一个值成员变量和用于设置值和获取值的槽函数 incrementValue() 和 getValue()。在 incrementValue() 函数中,我们使用 QWriteLocker 进行加锁操作,以确保这个共享数据只会被一个线程访问,在循环内对值进行加 1 操作,然后发射 valueChanged 信号,在 WorkerThread::run() 函数中,我们根据参数来执行对应的 MyObject 的增量操作或获取值操作。

#include <QCoreApplication>
#include <QDebug>
#include "objectthread.h"

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

    MyObject object;

    WorkerThread incrementThread(&object, true); // 用于增加共享值,加锁保护
    WorkerThread getThread(&object, false);     // 用于获取共享值,加锁保护

    QObject::connect(&object, &MyObject::valueChanged, [](int value) {
        qDebug() << "The new value is " << value;
    });

    incrementThread.start();
    getThread.start();

    incrementThread.wait();
    getThread.wait();



    return a.exec();
}

在这个示例中,我们创建了一个 MyObject 对象,同时创建了两个 WorkerThread 类型的线程,一个用于进行写入数据操作(增加值),另一个用于读取对象的值。等待线程执行完成后,程序结束。需要注意的是,在实际应用场景中,如果多个线程需要并发地读写同一个共享资源,并且存在竞争条件,那么就需要手动管理锁以避免数据不一致。

  • 0
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
Qt 是一个跨平台的应用程序开发框架,通过使用 C++ 编程语言Qt 库,开发者可以很方便地进行应用程序的开发Qt 提供了一系列的线程类和同步机制,用于实现多线程编程。在多线程编程中,为了保证共享资源的安全性,我们经常需要使用加锁的方式来进行同步操作。 单例模式是一种设计模式,在一个程序中只能存在一个类的对象实例。Qt 中的单例模式通常用于全局共享资源的管理,比如日志记录器、数据库管理器等。 在 Qt 中实现单例模式时,为了保证线程安全,我们需要加锁来控制多线程间对单例对象的访问。Qt 提供了 QMutex 类和 QMutexLocker 类用于加锁。 QMutex 是一个互斥量类,通过调用其 lock() 函数可以对资源加锁,这样其他线程就无法同时访问该资源。当线程完成对共享资源的操作后,需要调用 unlock() 函数来释放锁定。 使用 QMutex 加锁来实现单例模式的代码示例如下: ```cpp class Singleton { public: static Singleton* getInstance() { if (!m_instance) { QMutexLocker locker(&m_mutex); if (!m_instance) { m_instance = new Singleton(); } } return m_instance; } private: Singleton() {} static QMutex m_mutex; static Singleton* m_instance; }; QMutex Singleton::m_mutex; Singleton* Singleton::m_instance = nullptr; ``` 在 getInstance() 函数中,首先判断 m_instance 是否为空,如果为空,则使用 QMutexLocker 对象锁定 m_mutex,并再次检查 m_instance 是否为空。这样可以确保多个线程同时调用 getInstance() 函数时只有一个线程能够创建单例对象。 通过使用 QMutex 加锁来实现单例模式,可以保证在多线程环境下单例对象的安全性,避免了多线程访问带来的竞争问题。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

zw_ggr_2017

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值