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.類不提供訪問實例中成員的方法,而是通過信號槽機制,進行線程間的數據共享