通过这个文档,可以了解重进入和线程安全的类以及这些函数是如何在多线程的应用程序中被使用的
- 线程安全可以称之为是多线程并发操作,即很多线程在访问同一个共享的数据,因为所有的在共享数据中的操作是序列化的
- 一个重进入的函数可以被称为这个函数同事被多个线程所调用,但是他们其中只有一个线程可以拥有这个函数所拥有的数据
因此,一个线程安全的函数通常是可重入函数,但是一个可重入函数通常不是线程安全的
补充一下,如果一个类的成员函数可以被多个线程安全的调用,只要每个线程都在用同一个类的不同的实例,我们就可以称这个函数是可重入的;如果一个类的成员函数可以被多个线程安全的调用,那么我们称这个类是线程安全的,即使所有的线程都用一个类的同样的实例
注意:只有声明为线程安全的类才可以使用在多线程应用中,如果一个函数被声明为非线程安全或者非可重入函数,则这个函数不能被用在多线程应用中,这个类的实例也如此。
可重入的概念(同类不同对象)
C++的类通常是可重入的,尤其是类内的成员变量,一个线程可以调用可以可以重入类的成员函数,知道没有其他的线程来调用同样的实例的函数,举个例子,下面的Counter类是可重用的
class Counter
{
public:
Counter() { n=0;}
void increment() {++n}
void decrement() {--n}
int value() const {return n)
private:
int n;
};
但是这个类不是线程安全的,因为如果多个线程尝试来修改数据成员n,结果是不可意料的,因为++操作和–操作都操作的是这个实例中的同一个原子,换句话说,他们都操作者同一个地址中的数据
- 在一个内存地址中加载这个变量的值
- 增加或者删除这个变量的值
- 将这个内存中的值返回到主程序中
如果线程A和线程B同时加这个变量,同时的增加这个变量,然后又存放回去,同时存在重写的情况,那么这个变量最后只增加了一次 - 同类的不同实例对象,不同对象在不同的内存块,类的成员函数局部变量使用栈,线程调用时,局部变量数据入调用线程的栈,类的成员函数,或者使用栈或者使用堆,不同线程调用不同的对象的栈或者堆不会破坏内存块。
线程安全的概念(同类同对象)
毫无疑问,每一个线程都是独立的,线程A一定会执行上面的3步而且不能被打断,然后线程B再执行同样的操作,换一种方式的话,我们可以使用QMutex类来保证线程是安全的,而且可以白虎数据成员访问的安全性
class Counter
{
public:
Counter() {n = 0;}
void invrement() {QMutexLocker locker(&mutex): ++n}
void decrement() {QMutexLocker locker(&mutex): --n}
int value() const {QMutexLocker locker (&mutex): return n;)
private:
mutable QMutex mutex;
int n;
};
QMutexLocker类会在他的构造函数中锁住这个mutex然后在析构的时候对Mutex进行解锁,在函数的最后,我们锁住这个mutex来确保不同的线程可以一个一个的来访问这个变量,mutex的数据成员可以带着mutable来声明,因为我们需要在value()函数中对其进行加锁和解锁的操作,这是一个常函数。
类可重入,但如果多个线程调用同一对象的成员函数,多个线程写会导致对象成员变量的不同步。
在Qt中的注意事项
很多Qt的类是可重入的,但是他们一般都不是线程安全的,因为线程安全需要额外的开销,举个例子,QString是可重入的类,但是不是线程安全的,你可以很安全的在不同的线程中访问不同的QString的实例,但是不能再多个线程中同时访问一个实例
一些Qt的类的函数是线程安全的,他们多是线程相关的类(比如:QMutex)和基本函数(比如:QCoreApplication::postEvent())
注意:实际上多线程也不是完全独立的内存空间,POSIX用重进入的函数的定义线程安全的定义,这一点和C的APIs不太一样,当我们使用其他的Qt的原生的C++的类库的时候,确定要理解其定义。