概述:
线程安全: 一个线程安全的函数可以同时被多个线程调用,即便是它们使用来了共享数据。因为该共享数据的所有实例都被序列化了。(实例序列化是指这个实例已经被设置为顺序(串行)的访问)
可重入: 一个可重入的函数也可以同时被多个线程调用,但是只能是在每个调用使用自己的数据的情况下。
推广: 一个线程安全的函数总是可重入的,但是一个可重入的函数不总是线程安全的。推而广之,如果每个线程使用一个类的不同实例,则该类的成员函数可以被多个线程安全地调用,那么这个类被称为可重入的;如果即使所有的线程使用一个类的相同的实例,该类的成员函数也可以被多个线程安全地调用,那么这个类被称为线程安全的。
可重入案例:
1 class Counter
2 {
3 public:
4 Counter(){n = 0;}
5 void increment(){++n;}
6 void decrement(){--n;}
7 int value() const{return n;}
8 private:
9 int n;
10 };
解读: 显然上述类是可重入的,因为单个线程访问这个类的方法,变量n不会出现不安全的情况,但多个线程访问这个类的同一实例的方法,显然n变量的变化是不确定的,所以这个类不是线程安全的。
线程安全的案例:
1 class Counter
2 {
3 public:
4 Counter(){n=0;}
5 void increment(){QMutexLocker locker(&mutex); ++n;}
6 void decrement(){QMutexLocker locker(&mutex); --n;}
7 int value() const {QMutexLocker locker(&mutex); return n;}
8 private:
9 mutable QMutex mutex;
10 int n;
11 };
解读: 将上个类变成线程安全的一个简单方法就是使用QMutex来保护对数据成员的所有访问;这里的QMutexLocker类在其构造函数中自动锁住mutex,然后在析构函数进行调用时对其进行解锁,在函数结束时会调用析构函数。锁住mutex确保了不同线程的访问可以序列化进行。数据成员mutex使用了mutable限定符,是因为需要在value()函数中对mutex进行加锁和解锁,而它是一个const函数。
QObject的可重入
QObject是可重入的。它的大多数非GUI子类,例如,QTime、QTcpSocket、QUdpSocket和QProcess,也都是可重入的,可以在多个线程中同时使用这些类。注意,这些类是被设计为在单一的线程中进行创建和使用的,在一个线程中创建一个对象,然后在另外一个线程中调用这个对象的一个函数是无法保证一定可以工作的。需要注意以下3个约束条件:
1.QObject的子对象必须在创建它的父对象的线程中创建(即子对象在父对象所在线程中创建)。这意味着,永远不要将QThread对象(this)作为在该线程中创建的对象的父对象(因为QThread对象本身是在其他线程中创建的。(这句话怎么理解呢:就是不能将this作为父对象,因为this所在线程是其他线程(一般是主线程))
2.事件驱动对象只能在单一的线程中使用。具体来说,这条规则应用在定时器机制和网络模块中。例如,不可以在对象所在的线程(QObject::thread())以外的其他线程中启动一个定时器或者连接一个套接字。
3.必须确保删除QThread对象之前删除在该线程中创建的所有对象,可以通过在run函数中的栈上创建对象来保证这一点。
虽然QObject是可重入的,但是对于GUI类,尤其是QWidget及其所有子类,是不可重入的,它们只能在主线程中使用。QCoreApplication::exec()也必须在主线程中调用。在实际应用中,无法在主线程以外的线程中使用GUI类的问题,可以简单地通过这样的方式来解决:将一些非常耗时的操作放在一个单独的工作线程中来进行,等该工作线程完成后将结果返回给主线程,最后由主线程将结果显示到屏幕上。