재진입과 스레드 안전

문서 전체에서 재진입과 스레드 안전이라는 용어는 클래스와 함수를 표시하는 데 사용되어 멀티스레드 애플리케이션에서 사용할 수 있는 방법을 나타냅니다:

  • 스레드 안전 함수는 공유 데이터에 대한 모든 참조가 직렬화되므로 호출이 공유 데이터를 사용하는 경우에도 여러 스레드에서 동시에 호출할 수 있습니다.
  • 재진입 함수도 여러 스레드에서 동시에 호출할 수 있지만 각 호출이 자체 데이터를 사용하는 경우에만 가능합니다.

따라서 스레드 안전 함수는 항상 재진입 가능하지만, 재진입 함수가 항상 스레드 안전하지는 않습니다.

더 확장하면, 각 스레드가 클래스의 다른 인스턴스를 사용하는 한 여러 스레드에서 멤버 함수를 안전하게 호출할 수 있는 경우 클래스를 재진입이라고 합니다. 모든 스레드가 동일한 클래스 인스턴스를 사용하더라도 여러 스레드에서 멤버 함수를 안전하게 호출할 수 있다면 해당 클래스는 스레드 안전합니다.

참고: Qt 클래스는 여러 스레드에서 사용하도록 의도된 경우에만 스레드 안전으로 문서화됩니다. 함수가 스레드 안전 또는 재진입으로 표시되지 않은 경우, 다른 스레드에서 사용해서는 안 됩니다. 클래스가 스레드 안전 또는 재진입으로 표시되지 않은 경우 해당 클래스의 특정 인스턴스는 다른 스레드에서 액세스해서는 안 됩니다.

재진입

C++ 클래스는 자체 멤버 데이터에만 액세스하기 때문에 재진입이 자주 발생합니다. 다른 스레드가 동시에 클래스의 동일한 인스턴스에서 멤버 함수를 호출할 수 없는 한, 모든 스레드는 재진입 클래스의 인스턴스에서 멤버 함수를 호출할 수 있습니다. 예를 들어 아래 Counter 클래스는 재진입 클래스입니다:

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

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

private:
    int n;
};

여러 스레드가 데이터 멤버 n 를 수정하려고 하면 결과가 정의되지 않으므로 이 클래스는 스레드 안전하지 않습니다. 이는 ++-- 연산자가 항상 원자적인 것은 아니기 때문입니다. 실제로 이들은 보통 3개의 기계어 명령어로 확장됩니다:

  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;
};

QMutexLocker 클래스는 생성자에서 뮤텍스를 자동으로 잠그고 소멸자가 호출될 때, 즉 함수가 끝날 때 잠금을 해제합니다. 뮤텍스를 잠그면 다른 스레드에서 액세스하는 것이 직렬화됩니다. mutex 데이터 멤버가 mutable 한정자와 함께 선언된 이유는 생성자 함수인 value() 에서 뮤텍스를 잠그고 잠금을 해제해야 하기 때문입니다.

Qt 클래스에 대한 참고 사항

많은 Qt 클래스는 재진입 가능하지만 스레드 안전하지 않습니다. 왜냐하면 스레드 안전하게 만들면 QMutex 을 반복적으로 잠그고 해제하는 추가 오버헤드가 발생하기 때문입니다. 예를 들어 QString 은 재진입 가능하지만 스레드 안전하지 않습니다. 여러 스레드에서 동시에 QString다른 인스턴스에 안전하게 액세스할 수 있지만, 여러 스레드에서 동시에 QString동일한 인스턴스에 안전하게 액세스할 수는 없습니다( QMutex 로 액세스를 직접 보호하지 않는 한).

일부 Qt 클래스와 함수는 스레드 안전합니다. 주로 스레드 관련 클래스(예: QMutex)와 기본 함수(예: QCoreApplication::postEvent())가 여기에 해당합니다.

참고: 멀티스레딩 영역의 용어는 완전히 표준화되어 있지 않습니다. POSIX는 재진입 및 스레드 안전에 대한 정의를 C API에 대해 다소 다르게 사용합니다. Qt와 함께 다른 객체 지향 C++ 클래스 라이브러리를 사용할 때는 해당 정의를 이해해야 합니다.

© 2025 The Qt Company Ltd. Documentation contributions included herein are the copyrights of their respective owners. The documentation provided herein is licensed under the terms of the GNU Free Documentation License version 1.3 as published by the Free Software Foundation. Qt and respective logos are trademarks of The Qt Company Ltd. in Finland and/or other countries worldwide. All other trademarks are property of their respective owners.