重入和线程安全

纵观文档编制,术语 可重入 and thread-safe 用于标记类和函数,以指示它们如何用于多线程应用程序:

  • A thread-safe function can be called simultaneously from multiple threads, even when the invocations use shared data, because all references to the shared data are serialized.
  • A 可重入 function can also be called simultaneously from multiple threads, but only if each invocation uses its own data.

因此, thread-safe 函数始终 可重入 ,但 可重入 函数并不始终 thread-safe .

By extension, a class is said to be 可重入 if its member functions can be called safely from multiple threads, as long as each thread uses a different instance of the class. The class is thread-safe if its member functions can be called safely from multiple threads, even if all the threads use the same instance of the class.

注意: Qt classes are only documented as thread-safe if they are intended to be used by multiple threads. If a function is not marked as thread-safe or reentrant, it should not be used from different threads. If a class is not marked as thread-safe or reentrant then a specific instance of that class should not be accessed from different threads.

重入

C++ 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!

线程安全

Clearly, 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.

Qt 类注意事项

很多 Qt 类 可重入 ,但它们并不 thread-safe ,因为使它们线程安全会招致额外开销,如重复锁定和解锁 QMutex 。例如, QString 可重入,但并不线程安全。可以安全访问 different 实例化的 QString 同时从多个线程,但无法安全访问 same 实例化的 QString 同时从多个线程 (除非保护访问自身采用 QMutex ).

Some Qt classes and functions are thread-safe. These are mainly the thread-related classes (e.g. QMutex ) and fundamental functions (e.g. QCoreApplication::postEvent ()).

注意: Terminology in the multithreading domain isn't entirely standardized. POSIX uses definitions of reentrant and thread-safe that are somewhat different for its C APIs. When using other object-oriented C++ class libraries with Qt, be sure the definitions are understood.