在本文中,主要讲述Reentrancy and Thread-Safety机制,中文意思是可重入和线程安全,以指示它们如何在多线程应用程序中使用。
对于线程安全的函数, 即使调用使用共享数据,也可以被多个线程同时调用,因为对共享数据的所有引用都是序列化的。
可重入函数也可以从多个线程同时调用,但只有在每次调用都使用自己的数据时才可以。
因此,线程安全的函数总是可重入的,但可重入的函数并不总是线程安全的。
综上可知,如果一个类的成员函数可以从多个线程安全地调用,那么这个类就是可重入的,只要每个线程使用该类的不同实例。如果可以从多个线程安全地调用该类的成员函数,即使所有线程都使用该类的同一个实例,该类也是线程安全的。例如QTcpSocket,Qt助手的描述:All functions in this class are reentrant.
ReentrancyC++ classes are often reentrant, simply because they only access their own member data. Any thread can call a member function on an instance of a reentrant class, as long as no other thread can call a member function on the same instance of the class at the same time. For example, the Counter class below is reentrant:
class Counter
{
public:
Counter() { n = 0; }
void increment() { ++n; }
void decrement() { --n; }
int value() const { return n; }
private:
int n;
};
The class isn't thread-safe, because if multiple threads try to modify the data member n, the result is undefined. This is because the ++ and -- operators aren't always atomic. Indeed, they usually expand to three machine instructions: (1)Load the variable's value in a register. (2)Increment or decrement the register's value. (3)Store the register's value back into main memory. If thread A and thread B load the variable's old value simultaneously, increment their register, and store it back, they end up overwriting each other, and the variable is incremented only once!
Thread-SafetyClearly, the access must be serialized: Thread A must perform steps 1, 2, 3 without interruption (atomically) before thread B can perform the same steps; or vice versa. An easy way to make the class thread-safe is to protect all access to the data members with a 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;
};
The QMutexLocker class automatically locks the mutex in its constructor and unlocks it when the destructor is invoked, at the end of the function. Locking the mutex ensures that access from different threads will be serialized. The mutex data member is declared with the mutable qualifier because we need to lock and unlock the mutex in value(), which is a const function.
Notes on Qt Classes许多Qt类是可重入的,但它们不是线程安全的,因为使它们线程安全会导致重复锁定和解锁的额外开销。例如,QString是可重入的,但不是线程安全的。您可以同时从多个线程安全地访问不同的QString实例,但是不能同时从多个线程安全地访问相同的QString实例(除非您使用QMutex保护自己的访问)。
一些Qt类和函数是线程安全的。这些主要是与线程相关的类(例如QMutex)和基本函数(例如QCoreApplication::postEvent())。
注意:多线程领域中的术语并不完全标准化。POSIX使用可重入和线程安全的定义,这与它的C api有些不同。在Qt中使用其他面向对象的c++类库时,请确保理解这些定义。
注:
以上信息,来自于Qt帮助文档Qt Version 5.12.4,部分是我翻译的,有不正当之处请谅解,或请评论指出。