스레딩 기본 사항

스레드란 무엇인가요?

스레드는 프로세스와 마찬가지로 병렬로 작업을 수행하는 것입니다. 그렇다면 스레드는 프로세스와 어떻게 다를까요? 스프레드시트에서 계산을 하는 동안 같은 데스크톱에서 좋아하는 노래를 재생하는 미디어 플레이어가 실행되고 있을 수도 있습니다. 다음은 스프레드시트 프로그램을 실행하는 프로세스와 미디어 플레이어를 실행하는 프로세스 두 개가 병렬로 작동하는 예시입니다. 멀티태스킹은 이를 가리키는 잘 알려진 용어입니다. 미디어 플레이어를 자세히 살펴보면 하나의 프로세스 내에서 다시 한 번 병렬로 작업이 진행되고 있음을 알 수 있습니다. 미디어 플레이어가 오디오 드라이버로 음악을 전송하는 동안 모든 기능을 갖춘 사용자 인터페이스는 지속적으로 업데이트되고 있습니다. 이것이 바로 단일 프로세스 내의 동시성을 위한 스레드의 기능입니다.

그렇다면 동시성은 어떻게 구현될까요? 단일 코어 CPU에서의 병렬 작업은 영화에서 움직이는 이미지의 환영과 다소 유사한 착시 현상입니다. 프로세스의 경우 아주 짧은 시간 후에 프로세서가 한 프로세스의 작업을 중단함으로써 착시를 일으킵니다. 그런 다음 프로세서는 다음 프로세스로 이동합니다. 프로세스 간 전환을 위해 현재 프로그램 카운터가 저장되고 다음 프로세서의 프로그램 카운터가 로드됩니다. 레지스터와 특정 아키텍처 및 OS별 데이터에 대해서도 동일한 작업을 수행해야 하므로 이것만으로는 충분하지 않습니다.

하나의 CPU가 두 개 이상의 프로세스를 구동할 수 있는 것처럼, 하나의 프로세스의 서로 다른 두 코드 세그먼트에서 CPU가 실행되도록 할 수도 있습니다. 프로세스가 시작되면 항상 하나의 코드 세그먼트가 실행되므로 프로세스에는 하나의 스레드가 있다고 합니다. 그러나 프로그램이 두 번째 스레드를 시작하기로 결정할 수 있습니다. 그러면 하나의 프로세스 내에서 두 개의 서로 다른 코드 시퀀스가 동시에 처리됩니다. 단일 코어 CPU에서는 프로그램 카운터와 레지스터를 반복적으로 저장한 다음 다음 스레드의 프로그램 카운터와 레지스터를 로드함으로써 동시성을 달성할 수 있습니다. 활성 스레드 사이를 순환하는 데 프로그램의 협조가 필요하지 않습니다. 다음 스레드로 전환할 때 스레드는 어떤 상태일 수 있습니다.

현재 CPU 설계의 추세는 여러 개의 코어를 사용하는 것입니다. 일반적인 단일 스레드 애플리케이션은 하나의 코어만 사용할 수 있습니다. 그러나 여러 스레드를 가진 프로그램은 여러 코어에 할당할 수 있으므로 진정한 의미의 동시 작업을 수행할 수 있습니다. 결과적으로 작업을 둘 이상의 스레드에 분산하면 추가 코어를 사용할 수 있기 때문에 멀티코어 CPU에서 프로그램을 훨씬 빠르게 실행할 수 있습니다.

GUI 스레드와 워커 스레드

앞서 언급했듯이 각 프로그램은 시작될 때 하나의 스레드를 갖습니다. 이 스레드를 "메인 스레드"(Qt 애플리케이션에서는 "GUI 스레드"라고도 함)라고 합니다. Qt GUI 은 이 스레드에서 실행되어야 합니다. 모든 위젯과 여러 관련 클래스(예: QPixmap)는 보조 스레드에서 작동하지 않습니다. 보조 스레드는 메인 스레드에서 처리 작업을 오프로드하는 데 사용되기 때문에 일반적으로 "작업자 스레드"라고 합니다.

데이터에 대한 동시 액세스

각 스레드에는 자체 스택이 있으므로 각 스레드에는 자체 호출 기록과 로컬 변수가 있습니다. 프로세스와 달리 스레드는 동일한 주소 공간을 공유합니다. 다음 다이어그램은 스레드의 빌딩 블록이 메모리에 어떻게 위치하는지 보여줍니다. 프로그램 카운터와 비활성 스레드의 레지스터는 일반적으로 커널 공간에 보관됩니다. 코드의 공유 복사본과 각 스레드에 대한 별도의 스택이 있습니다.

"Thread visualization"

두 스레드가 동일한 객체에 대한 포인터를 가지고 있는 경우 두 스레드가 동시에 해당 객체에 액세스할 수 있으며 이로 인해 잠재적으로 객체의 무결성이 손상될 수 있습니다. 동일한 객체의 두 메서드가 동시에 실행될 때 잘못될 수 있는 많은 일들을 쉽게 상상할 수 있습니다.

서로 다른 스레드에서 하나의 객체에 액세스해야 할 때가 있습니다. 예를 들어 서로 다른 스레드에 있는 객체가 통신해야 할 때입니다. 스레드는 동일한 주소 공간을 사용하기 때문에 프로세스보다 스레드가 데이터를 교환하는 것이 더 쉽고 빠릅니다. 데이터를 직렬화하거나 복사할 필요가 없습니다. 포인터 전달은 가능하지만 어떤 스레드가 어떤 객체에 닿는지에 대한 엄격한 조정이 있어야 합니다. 하나의 객체에 대한 동시 작업 실행을 방지해야 합니다. 이를 달성하는 방법에는 여러 가지가 있으며 그 중 일부는 아래에 설명되어 있습니다.

그렇다면 어떻게 하면 안전하게 할 수 있을까요? 스레드에서 생성된 모든 객체는 다른 스레드에 참조가 없고 객체가 다른 스레드와 암시적 결합을 하지 않는다면 해당 스레드 내에서 안전하게 사용할 수 있습니다. 이러한 암시적 결합은 정적 멤버, 싱글톤 또는 전역 데이터와 같이 인스턴스 간에 데이터가 공유될 때 발생할 수 있습니다. 스레드 안전과 재진입 클래스 및 함수의 개념을 숙지하세요.

스레드 사용

스레드에는 기본적으로 두 가지 사용 사례가 있습니다:

  • 멀티코어 프로세서를 사용하여 처리 속도를 높입니다.
  • 오래 지속되는 처리를 오프로드하거나 다른 스레드에 대한 호출을 차단하여 GUI 스레드 또는 기타 시간이 중요한 스레드의 응답성을 유지합니다.

스레드 대신 대안을 사용해야 하는 경우

개발자는 스레드를 매우 신중하게 사용해야 합니다. 다른 스레드를 시작하기는 쉽지만 모든 공유 데이터가 일관성을 유지하도록 하는 것은 매우 어렵습니다. 문제가 가끔씩만 나타나거나 특정 하드웨어 구성에서만 나타날 수 있기 때문에 문제를 찾기가 어려운 경우가 많습니다. 특정 문제를 해결하기 위해 스레드를 만들기 전에 가능한 대안을 고려해야 합니다.

대안댓글
QEventLoop::processEvents()시간이 많이 걸리는 계산 중에 QEventLoop::processEvents()를 반복적으로 호출하면 GUI 차단을 방지할 수 있습니다. 그러나 이 솔루션은 하드웨어에 따라 processEvents() 호출이 너무 자주 발생하거나 충분히 자주 발생하지 않을 수 있기 때문에 확장성이 좋지 않습니다.
QTimer타이머를 사용하여 미래의 특정 시점에 슬롯 실행을 예약하면 백그라운드 처리를 편리하게 수행할 수 있습니다. 간격이 0인 타이머는 처리할 이벤트가 더 이상 없는 즉시 시간 초과됩니다.
QSocketNotifier QNetworkAccessManager QIODevice::readyRead()이는 느린 네트워크 연결에서 하나 또는 여러 개의 스레드를 각각 차단 읽기 기능을 갖는 대신 사용할 수 있습니다. 네트워크 데이터 청크에 대한 응답으로 계산을 빠르게 실행할 수 있다면 이 반응형 설계가 스레드에서 동기식으로 대기하는 것보다 낫습니다. 반응형 설계는 스레딩보다 오류가 덜 발생하고 에너지 효율적입니다. 많은 경우 성능상의 이점도 있습니다.

일반적으로 안전하고 검증된 경로만 사용하고 임시 스레딩 개념을 도입하지 않는 것이 좋습니다. QtConcurrent 모듈은 프로세서의 모든 코어에 작업을 분배하기 위한 간편한 인터페이스를 제공합니다. 스레딩 코드는 QtConcurrent 프레임워크에 완전히 숨겨져 있으므로 세부 사항을 관리할 필요가 없습니다. 그러나 QtConcurrent 은 실행 중인 스레드와 통신이 필요한 경우 사용할 수 없으며, 차단 작업을 처리하는 데 사용해서는 안 됩니다.

어떤 Qt 스레드 기술을 사용해야 하나요?

Qt의 멀티스레딩 기술에 대한 다양한 접근 방식에 대한 소개와 그 중에서 선택하는 방법에 대한 지침은 Qt의 멀티스레딩 기술 페이지를 참조하십시오.

Qt 스레드 기초

다음 섹션에서는 QObject가 스레드와 상호 작용하는 방법, 프로그램이 여러 스레드의 데이터에 안전하게 액세스하는 방법, 비동기 실행이 스레드를 차단하지 않고 결과를 생성하는 방법에 대해 설명합니다.

QObject와 스레드

위에서 언급했듯이 개발자는 다른 스레드에서 객체의 메서드를 호출할 때 항상 주의해야 합니다. Thread affinity 이 상황은 변하지 않습니다. Qt 문서에서는 여러 메서드를 스레드 안전하다고 표시하고 있습니다. postEvent()가 주목할 만한 예입니다. 스레드 안전 메서드는 다른 스레드에서 동시에 호출될 수 있습니다.

일반적으로 메서드에 대한 동시 액세스가 없는 경우, 다른 스레드에서 스레드 안전하지 않은 객체의 메서드를 호출하면 동시 액세스가 발생하기 전에 수천 번 작동하여 예기치 않은 동작이 발생할 수 있습니다. 테스트 코드를 작성한다고 해서 스레드 정확성이 완전히 보장되는 것은 아니지만 여전히 중요합니다. Linux에서는 Valgrind와 Helgrind가 스레딩 오류를 감지하는 데 도움이 될 수 있습니다.

데이터 무결성 보호

멀티스레드 애플리케이션을 작성할 때는 데이터 손상을 방지하기 위해 각별한 주의를 기울여야 합니다. 스레드를 안전하게 사용하는 방법에 대한 논의는 스레드 동기화를 참조하세요.

비동기 실행 처리하기

작업자 스레드의 결과를 얻는 한 가지 방법은 스레드가 종료될 때까지 기다리는 것입니다. 그러나 많은 경우 차단 대기는 허용되지 않습니다. 블로킹 대기의 대안으로 게시된 이벤트 또는 대기 중인 신호와 슬롯을 이용한 비동기 결과 전달이 있습니다. 이 경우 연산 결과가 다음 소스 줄에 나타나지 않고 소스 파일의 다른 곳에 위치한 슬롯에 나타나기 때문에 일정한 오버헤드가 발생합니다. 이러한 비동기 동작은 GUI 애플리케이션에서 사용되는 이벤트 중심 프로그래밍과 매우 유사하기 때문에 Qt 개발자는 이러한 종류의 비동기 동작을 사용하는 데 익숙합니다.

예제

Qt는 스레드 사용에 대한 몇 가지 예제를 제공합니다. 간단한 예제는 QThreadQThreadPool 의 클래스 참조를 참조하십시오. 보다 고급 예제는 스레딩 및 동시 프로그래밍 예제 페이지를 참조하십시오.

더 깊이 파고들기

스레딩은 매우 복잡한 주제입니다. Qt는 이 튜토리얼에서 소개한 것보다 더 많은 스레딩 클래스를 제공합니다. 다음 자료는 이 주제를 더 깊이 있게 살펴보는 데 도움이 될 수 있습니다:

© 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.