스레드와 QObject
QThread 는 QObject 을 상속합니다. 스레드가 실행을 시작하거나 완료했음을 나타내는 신호를 방출하고 몇 개의 슬롯도 제공합니다.
더 흥미로운 점은 QObject을 여러 스레드에서 사용할 수 있고, 다른 스레드의 슬롯을 호출하는 신호를 방출하며, 다른 스레드에 "살아있는" 객체에 이벤트를 게시할 수 있다는 것입니다. 이는 각 스레드에 고유한 이벤트 루프가 허용되기 때문에 가능합니다.
Q객체 재진입
QObject 는 재진입입니다. QTimer , QTcpSocket, QUdpSocket, QProcess 와 같은 대부분의 비-GUI 하위 클래스도 재진입이 가능하므로 여러 스레드에서 동시에 이러한 클래스를 사용할 수 있습니다. 이러한 클래스는 단일 스레드 내에서 생성 및 사용하도록 설계되었으므로 한 스레드에서 객체를 생성하고 다른 스레드에서 해당 함수를 호출해도 작동이 보장되지 않습니다. 주의해야 할 세 가지 제약 조건이 있습니다:
- QObject 의 자식은 항상 부모가 생성된 스레드에서 생성되어야 합니다. 이는 무엇보다도 QThread 객체(
this
)를 스레드에서 생성된 객체의 부모로 전달해서는 안 된다는 것을 의미합니다( QThread 객체 자체가 다른 스레드에서 생성되었으므로). - 이벤트 기반 객체는 단일 스레드에서만 사용할 수 있습니다. 특히 이는 타이머 메커니즘과 network module 에 적용됩니다. 예를 들어 object's thread 이 아닌 스레드에서는 타이머를 시작하거나 소켓을 연결할 수 없습니다.
- QThread 을삭제하기 전에 스레드에서 생성된 모든 객체가 삭제되었는지 확인해야 합니다. 이는 run() 구현에서 스택에 객체를 생성하면 쉽게 수행할 수 있습니다.
QObject 은 재진입하지만, 특히 QWidget 과 그 하위 클래스는 재진입하지 않습니다. 이러한 클래스는 메인 스레드에서만 사용할 수 있습니다. 앞서 언급했듯이 QCoreApplication::exec() 역시 해당 스레드에서 호출해야 합니다.
실제로 메인 스레드가 아닌 다른 스레드에서 GUI 클래스를 사용할 수 없는 문제는 시간이 많이 걸리는 작업을 별도의 작업자 스레드에 넣고 작업자 스레드가 완료되면 메인 스레드에서 결과를 화면에 표시하는 방식으로 쉽게 해결할 수 있습니다. 이 접근 방식은 만델브로트 예제와 블로킹 포춘 클라이언트 예제를 구현하는 데 사용되었습니다.
일반적으로 QApplication 앞에 QObject를 만드는 것은 지원되지 않으며 플랫폼에 따라 종료 시 이상한 충돌이 발생할 수 있습니다. 즉, QObject 의 정적 인스턴스도 지원되지 않습니다. 적절하게 구조화된 단일 또는 다중 스레드 애플리케이션은 QApplication 이 가장 먼저 생성되고 QObject 이 마지막으로 소멸되도록 해야 합니다.
스레드별 이벤트 루프
각 스레드에는 자체 이벤트 루프가 있을 수 있습니다. 초기 스레드는 QCoreApplication::exec()를 사용하여 이벤트 루프를 시작하거나 단일 다이얼로그 GUI 애플리케이션의 경우 QDialog::exec()를 사용하여 이벤트 루프를 시작하기도 합니다. 다른 스레드는 QThread::exec()를 사용하여 이벤트 루프를 시작할 수 있습니다. QCoreApplication 와 마찬가지로 QThread 은 exit(int) 함수와 quit() 슬롯을 제공합니다.
스레드의 이벤트 루프를 사용하면 이벤트 루프가 필요한 특정 비 Qt GUI 클래스(예: QTimer, QTcpSocket, QProcess)를 스레드에서 사용할 수 있습니다. 또한 모든 스레드의 신호를 특정 스레드의 슬롯에 연결할 수 있습니다. 이에 대해서는 아래 스레드 간 신호 및 슬롯 섹션에서 자세히 설명합니다.
QObject 인스턴스는 생성된 스레드에 존재한다고 합니다. 해당 객체에 대한 이벤트는 해당 스레드의 이벤트 루프에 의해 발송됩니다. QObject 인스턴스가 있는 스레드는 QObject::thread()를 사용하여 사용할 수 있습니다.
QObject::moveToThread() 함수는 객체와 그 자식에 대한 스레드 선호도를 변경합니다(객체에 부모가 있는 경우 객체를 이동할 수 없음).
객체를 소유한 스레드가 아닌 다른 스레드에서 QObject 에서 delete
을 호출하거나 다른 방식으로 객체에 액세스하는 것은 해당 객체가 그 순간 이벤트를 처리하고 있지 않다는 보장이 없는 한 안전하지 않습니다. 대신 QObject::deleteLater()를 사용하면 DeferredDelete 이벤트가 게시되어 해당 객체 스레드의 이벤트 루프가 결국 포착하게 됩니다. 기본적으로 QObject 을 소유하는 스레드는 QObject 을 생성하는 스레드이지만 QObject::moveToThread()가 호출된 후에는 그렇지 않습니다.
이벤트 루프가 실행되고 있지 않으면 이벤트가 개체에 전달되지 않습니다. 예를 들어 스레드에서 QTimer 객체를 만들지만 exec()을 호출하지 않는 경우 QTimer 는 timeout() 신호를 보내지 않습니다. deleteLater ()를 호출하는 것도 작동하지 않습니다. (이러한 제한 사항은 메인 스레드에도 적용됩니다.)
스레드 안전 함수 QCoreApplication::postEvent()를 사용하여 언제든지 모든 스레드의 모든 객체에 이벤트를 수동으로 게시할 수 있습니다. 이벤트는 해당 객체가 생성된 스레드의 이벤트 루프에 의해 자동으로 발송됩니다.
이벤트 필터는 모든 스레드에서 지원되지만 모니터링 객체가 모니터링 대상 객체와 동일한 스레드에 있어야 한다는 제한이 있습니다. 마찬가지로 QCoreApplication::sendEvent()( postEvent()와 달리)는 함수가 호출되는 스레드에 있는 개체에 이벤트를 디스패치하는 데만 사용할 수 있습니다.
다른 스레드에서 QObject 서브클래스 접근하기
QObject 와 그 서브클래스는 모두 스레드 안전하지 않습니다. 여기에는 전체 이벤트 전달 시스템이 포함됩니다. 다른 스레드에서 객체에 액세스하는 동안 이벤트 루프가 QObject 하위 클래스에 이벤트를 전달할 수 있다는 점을 염두에 두어야 합니다.
현재 스레드에 존재하지 않는 QObject 서브클래스에서 함수를 호출하고 있고 객체가 이벤트를 수신할 수 있는 경우 QObject 서브클래스의 내부 데이터에 대한 모든 액세스를 뮤텍스로 보호해야 하며 그렇지 않으면 충돌이나 기타 원치 않는 동작이 발생할 수 있습니다.
다른 객체와 마찬가지로 QThread 객체는 QThread::run()가 호출될 때 생성되는 스레드가 아니라 객체가 생성된 스레드에 존재합니다. 뮤텍스로 멤버 변수를 보호하지 않는 한 QThread 서브클래스에 슬롯을 제공하는 것은 일반적으로 안전하지 않습니다.
반면에 신호 방출은 스레드 안전하므로 QThread::run() 구현에서 신호를 안전하게 방출할 수 있습니다.
스레드 간 신호와 슬롯
Qt는 다음과 같은 신호-슬롯 연결 유형을 지원합니다:
- Auto Connection (기본값) 수신 객체가 친화성을 가진 스레드에서 신호가 방출되면 동작은 직접 연결과 동일합니다. 그렇지 않으면, 동작은 큐 연결과 동일합니다."
- Direct Connection 슬롯은 신호가 방출되면 즉시 호출됩니다. 슬롯은 반드시 수신자의 스레드가 아닌 발신자의 스레드에서 실행됩니다.
- Queued Connection 이 슬롯은 제어가 수신자 스레드의 이벤트 루프로 돌아갈 때 호출됩니다. 슬롯은 수신자의 스레드에서 실행됩니다.
- Blocking Queued Connection 슬롯이 반환될 때까지 현재 스레드 블록을 제외하고 큐 연결의 경우와 마찬가지로 슬롯이 호출됩니다.
참고: 이 유형을 사용하여 동일한 스레드에 있는 객체를 연결하면 교착 상태가 발생할 수 있습니다.
- Unique Connection 동작은 자동 연결과 동일하지만 기존 연결을 복제하지 않는 경우에만 연결이 이루어집니다. 즉, 동일한 신호가 동일한 객체 쌍에 대해 동일한 슬롯에 이미 연결되어 있는 경우 연결이 이루어지지 않고 connect()가
false
를 반환합니다.
연결 유형은 connect()에 추가 인수를 전달하여 지정할 수 있습니다. 발신자와 수신자가 다른 스레드에 있을 때 직접 연결을 사용하는 것은 다른 스레드에 있는 객체에서 함수를 호출하는 것이 안전하지 않은 것과 같은 이유로 수신자의 스레드에서 이벤트 루프가 실행 중인 경우 안전하지 않다는 점에 유의하세요.
QObject::connect() 자체는 스레드 안전합니다.
만델브로트 예제는 작업자 스레드와 메인 스레드 간의 통신을 위해 대기열 연결을 사용합니다. 메인 스레드의 이벤트 루프(그리고 결과적으로 애플리케이션의 사용자 인터페이스)가 멈추는 것을 방지하기 위해 모든 만델브로트 프랙탈 계산은 별도의 작업자 스레드에서 수행됩니다. 이 스레드는 프랙탈 렌더링이 완료되면 신호를 보냅니다.
마찬가지로 차단 포춘 클라이언트 예시에서는 별도의 스레드를 사용하여 비동기적으로 TCP 서버와 통신합니다.
© 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.