如何保证QT中类的线程安全?(让多线程不再崩溃)

1 什么是类的线程安全(或线程安全的类)?

了解多线程的人大概都知道,类的线程安全比可重入更加严格,它要求在不同线程同时调用类同一实例的成员函数,而不会引发程序的崩溃。

2 哪些情况下不用考虑线程安全问题?

2.1 在多线程中对bool,int,float,QString等类型的操作,你不用考虑任何安全性问题。

因为你无论以什么方式在不同线程中对这些类型进行操作,都像真正的原子性操作一样(其实不是严格的原子性操作),完全不会引起程序崩溃。我想这可能是QT已经对这些类型进行了线程安全保障。
比如,我下面的这个类本身就是线程安全的:

class Counter : public QObject
{
    Q_OBJECT
public:
    Counter() { n = 0; }
    void O1() { ++n; }
    void O2() { --n; }
    int get() const { return n; }
private:
    int n;
};

2.2 在多线程中使用信号槽机制进行资源共享时,不用考虑线程安全问题。

例如在两个不同的子线程中通过信号槽将字符串信息传递到UI线程,用于更新界面的显示,这个操作是线程安全的,因为信号槽作为QT独创的通信机制,是线程安全的。

3 设计一个线程安全的类

3.1 先展示一个未进行线程安全保障的类是如何引发崩溃的

定义一个非线程安全的类:

class NotSafe : public QObject
{
    Q_OBJECT
public:
    NotSafe() { }
    void O1() { list.append(1); list.append(2); }
    void O2() { list.append(3); list.clear(); }
    QList<int> get() const { return list; }
private:
    QList<int> list; //对QList的操作不是线程安全的
};

定义两个线程:

class Thread1: public QThread
{
public:
    Thread1();
    virtual void run() override;
};
class Thread2: public QThread
{
public:
    Thread2();
    virtual void run() override;
};

在两个子线程中对这个类的同一实例进行访问:

NotSafe notsafe; //同一实例
void Thread1::run()
{
    while(1){
        QApplication::processEvents();
        notsafe.O1(); //可能引发崩溃的操作
        notsafe.O1(); //可能引发崩溃的操作
        qDebug()<<notsafe.get(); //可能引发崩溃的操作
    }
}
void Thread2::run()
{
    while(1){
        QApplication::processEvents();
        notsafe.O1(); //可能引发崩溃的操作
        notsafe.O2(); //可能引发崩溃的操作
        qDebug()<<notsafe.get(); //可能引发崩溃的操作
    }
}

不出意外,程序运行一分钟以内就会崩溃。

3.2 安全写法1

使用互斥量QMutex,并使用 lock() 和 unlock() 方法进行锁定和解锁,但这个方法难以对返回参数进行保护

class Safe1 : public QObject
{
    Q_OBJECT
public:
    Safe1() { }
    void O1() { mutex.lock(); list.append(1); list.append(2); mutex.unlock();}
    void O2() { mutex.lock(); list.append(3); list.clear(); mutex.unlock();}
    QList<int> get() const { QMutexLocker locker(&mutex); return list; }
private:
    mutable QMutex mutex;
    QList<int> list;
};

3.2 安全写法2

使用互斥量QMutex,并使用 QMutexLocker 进行锁定,当 QMutexLocker 从栈释放后,自动解锁

class Safe2 : public QObject
{
    Q_OBJECT
public:
    Safe2() { }
    void O1() { QMutexLocker locker(&mutex); list.append(1); list.append(2); }
    void O2() { QMutexLocker locker(&mutex); list.append(3); list.clear(); }
    QList<int> get() const { QMutexLocker locker(&mutex); return list; }
private:
    mutable QMutex mutex;
    QList<int> list;
};

3.3 安全写法3

使用读写锁 QReadWriteLock,并使用 lockForRead() 和 lockForWrite() 对读或写进行保护,这种方法相比QMutex效率更高,因为它允许多线程同时读。

class Safe3 : public QObject
{
    Q_OBJECT
public:
    Safe3() { }
    void O1() { QWriteLocker locker(&RWlock); list.append(1); list.append(2); }
    void O2() { QWriteLocker locker(&RWlock); list.append(3); list.clear(); }
    QList<int> get() const { QReadLocker locker(&RWlock); return list; }
private:
    mutable QReadWriteLock RWlock;
    QList<int> list;
};

3.4 更多保证类的线程安全性的办法

1.使用生产者 — 消费者模式,通过缓冲区产生多个数据的备份,可提高线程间数据共享的效率
2.使用socket进行线程间,甚至进程间的数据共享
3.类不提供访问实例中成员的方法,而是通过信号槽机制,进行线程间的数据共享

  • 6
    点赞
  • 50
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值