可重入和线程安全

谈谈编程中的基础概念:函数可重入和线程安全!

  • 文章原文来自于Qt官方文档:http://doc.qt.io/qt-5/threads-reentrancy.html “Qt原文”

综观整个文档,术语可重入和线程安全总是被用来标记类和函数,表明他们如何被用在多线程程序中。

一个线程安全的函数能被多个线程同时调用,甚至在这些调用使用共享数据时,因为所有对共享数据的引用是串行进行的。
一个可重入的函数也能被多个线程同时调用,但前提是每个调用只使用它们自己的数据。
所以,一个线程安全的函数总是可重入的, 但一个可重入的函数不总是线程安全的。

引申开来,如果一个类的成员函数能被多个线程安全地调用,那么这个类就被称为可重入, 只要每个线程各使用这个类的一个不同实例即可。
如果这个类的成员函数能被多个线程安全地调用,那么这个类就是线程安全的,即使所有线程都使用同一个类实例。

注意:如果Qt的类们企图被用在多线程环境中,那么他们只是被记录为线程安全地。如果一个函数没有被标记为线程安全或可重入,那么它也不应该被用在不同线程中。如果一个类没有被标记为线程安全或可重入,那么这种类的一个特定实例也不应该被不同的线程访问。

可重入

C++类通常都是可重入的,仅仅是因为他们脂肪纹他们自己的成员数据。任意线程都能调用一个可重入类实例上的一个成员函数,只要在同一时刻,不会有其他线程能调用同一实例上的一个成员函数。例如,下面的Counter类就是可重入的。

class Counter
{
public:
    Counter() { n = 0; }

    void increment() { ++n; }
    void decrement() { --n; }
    int value() const { return n; }

private:
    int n;
};

这个Counter类不是线程安全的,因为如果多个线程尝试修改数据成员 n , 结果是未定义的。这是因为++ 和– 操作不总是原子性的。实际上,
他们通常被扩展为第三方机器指令:

  1. 加载变量的值到一个寄存器。
  2. 增加或减少寄存器的值。
  3. 将寄存器的值存回主内存。

如果线程A和线程B同时加载了变量的旧值,增加了他们得寄存器,并且存回内存,最终他们会相互重写,并且那个变量将只会被增加一次!

线程安全

显然,访问必须是串行的:线程A必须无中断的执行完1 2 3步骤,在线程B能执行同样的步骤之前;反之亦然。使一个类线程安全的一个比较容易的方法是,用QMutex来保护所有的访问数据成员操作:

class Counter
{
public:
    Counter() { n = 0; }

    void increment() { QMutexLocker locker(&mutex); ++n; }
    void decrement() { QMutexLocker locker(&mutex); --n; }
    int value() const { QMutexLocker locker(&mutex); return n; }

private:
    mutable QMutex mutex;
    int n;
};

QMutex类在它的构造函数中自动锁定mutex对象 ,并且在它的析构函数结尾解锁mutex对象。锁定mutex可以保证来自不同线程的访问将被串行
执行。mutex数据成员用mutable限定词声明,因为我们需要锁定和解锁mutex对象,用 value()函数,这个函数是一个const函数。

Qt类注意

很多Qt类是可重入的,但他们并不是线程安全的,因为让他们线程安全会引发额外的开销,即反复锁定和解锁一个QMutex对象的开销。例如,
QString是可重入但不是线程安全的。你能安全地访问QString的不同实例,同时从多个线程,但是你不能安全地访问同一个QString实例,同时
从多个线程(除非你自己用一个QMutex来保护访问操作)。

一些Qt类和函数是线程安全的。这些类主要是线程相关的类(例如QMutex)和 基本函数(例如QCoreApplication::postEvent())。

注意:多线程领域的术语还没有完全标准化。POSIX使用可重入和线程安全这种定义,和它的C api有几分不同。当在Qt中使用其他面向对象C++类库时,需要确认定义是被理解的。

你可能感兴趣的:(Qt,Qt,多线程,可重入)