PyQt개발강좌, QThread를 이용한 멀티스레딩 처리

현대의 GUI 애플리케이션 개발에서 멀티스레딩은 매우 중요한 주제입니다. 사용자가 프로그램을 사용할 때, 애플리케이션은 반응성이 뛰어나야 합니다. 즉, 사용자가 버튼을 클릭하거나 다른 상호작용을 할 때, 애플리케이션은 지연 없이 즉각적으로 반응해야 합니다. 그러나 시간이 오래 걸리는 작업을 메인 스레드에서 수행하면, 프로그램은 일시 중지된 것처럼 느껴집니다. 이를 해결하기 위해 PyQt에서는 QThread를 사용하여 멀티스레딩을 구현할 수 있습니다.

QThread란?

QThread는 PyQt에서 스레드를 생성하고 제어하기 위한 클래스로, 시간 소모적인 작업을 별도의 스레드에서 실행할 수 있게 해줍니다. 메인 스레드에서 UI를 그리는 작업을 담당하고, 백그라운드 스레드에서 데이터를 처리하거나 파일을 읽는 등의 작업을 수행할 수 있습니다.

QThread의 특징

  • QThread는 Qt의 이벤트 루프와 통합되어 있습니다.
  • 각 QThread 객체는 자신의 이벤트 루프를 가질 수 있습니다.
  • 신호와 슬롯 메커니즘을 통해 스레드 간의 통신이 가능합니다.

QThread 사용 예제

이번 섹션에서는 PyQt와 QThread를 이용하여 간단한 멀티스레딩 프로그램을 만들어보겠습니다. 이 예제에서는 사용자가 버튼을 클릭하면 긴 작업(예: 10초 대기)을 백그라운드 스레드에서 실행하고, UI가 계속 반응할 수 있도록 할 것입니다.

필요한 모듈 임포트

from PyQt5.QtWidgets import QApplication, QWidget, QVBoxLayout, QPushButton, QLabel
from PyQt5.QtCore import QThread, pyqtSignal
import sys
import time

QThread 클래스를 상속하여 작업 정의하기

먼저, QThread 클래스를 상속하여 백그라운드에서 실행할 작업을 정의합니다. 이 예제에서는 ‘WorkerThread’라는 클래스를 만들고, ‘run’ 메서드에서 10초 동안 대기하는 작업을 수행합니다.

class WorkerThread(QThread):
    # 작업 완료 신호
    finished = pyqtSignal()

    def run(self):
        time.sleep(10)  # 10초 대기
        self.finished.emit()  # 작업 완료 신호 발송

메인 애플리케이션 클래스 정의하기

다음으로 UI와 스레드 간의 상호작용을 관리하기 위해 메인 애플리케이션 클래스를 정의합니다. 이 클래스에서는 버튼을 클릭하면 스레드를 시작하고, 작업이 완료되면 UI 업데이트를 수행합니다.

class MainWindow(QWidget):
    def __init__(self):
        super().__init__()
        self.setWindowTitle("PyQt QThread 예제")
        self.setGeometry(100, 100, 300, 200)

        self.layout = QVBoxLayout()
        self.button = QPushButton("작업 시작")
        self.label = QLabel("대기 중...")

        self.button.clicked.connect(self.start_work)

        self.layout.addWidget(self.label)
        self.layout.addWidget(self.button)
        self.setLayout(self.layout)

    def start_work(self):
        self.label.setText("작업 중...")
        self.thread = WorkerThread()  # 스레드 인스턴스 생성
        self.thread.finished.connect(self.work_finished)  # 신호 연결
        self.thread.start()  # 스레드 시작

    def work_finished(self):
        self.label.setText("작업 완료!")

메인 애플리케이션 실행하기

마지막으로, QApplication을 실행하고 메인 윈도우를 표시하는 코드를 작성합니다.

if __name__ == "__main__":
    app = QApplication(sys.argv)
    window = MainWindow()
    window.show()
    sys.exit(app.exec_())

완성된 코드

from PyQt5.QtWidgets import QApplication, QWidget, QVBoxLayout, QPushButton, QLabel
from PyQt5.QtCore import QThread, pyqtSignal
import sys
import time

class WorkerThread(QThread):
    finished = pyqtSignal()

    def run(self):
        time.sleep(10)  # 10초 대기
        self.finished.emit()  # 작업 완료 신호 발송

class MainWindow(QWidget):
    def __init__(self):
        super().__init__()
        self.setWindowTitle("PyQt QThread 예제")
        self.setGeometry(100, 100, 300, 200)

        self.layout = QVBoxLayout()
        self.button = QPushButton("작업 시작")
        self.label = QLabel("대기 중...")

        self.button.clicked.connect(self.start_work)

        self.layout.addWidget(self.label)
        self.layout.addWidget(self.button)
        self.setLayout(self.layout)

    def start_work(self):
        self.label.setText("작업 중...")
        self.thread = WorkerThread()  # 스레드 인스턴스 생성
        self.thread.finished.connect(self.work_finished)  # 신호 연결
        self.thread.start()  # 스레드 시작

    def work_finished(self):
        self.label.setText("작업 완료!")

if __name__ == "__main__":
    app = QApplication(sys.argv)
    window = MainWindow()
    window.show()
    sys.exit(app.exec_())

신호와 슬롯에 대해 더 알아보기

PyQt에서 중요한 개념 중 하나는 신호와 슬롯입니다. 서로 다른 스레드 간의 통신을 원활하게 하여 데이터를 안전하게 전달할 수 있습니다. QThread 클래스에서 발생된 신호를 메인 스레드에서 처리하기 위해 슬롯을 정의하는 방식으로 작업이 진행됩니다.

신호와 슬롯 예제

다음 예제에서는 작업 중에 진행 상황을 표시하기 위해 추가적으로 진행률 신호를 발생시키겠습니다. 이를 위해 WorkerThread 클래스에 ‘progress’ 신호를 추가하겠습니다.

class WorkerThread(QThread):
    finished = pyqtSignal()
    progress = pyqtSignal(int)  # 진행률 신호 추가

    def run(self):
        for i in range(10):
            time.sleep(1)  # 1초 대기
            self.progress.emit((i + 1) * 10)  # 진행률 신호 발송
        self.finished.emit()  # 작업 완료 신호 발송

이제 메인 애플리케이션에서 이 신호를 수신하여 진행률을 표시하겠습니다.

class MainWindow(QWidget):
    # ... 기존 코드 생략 ...

    def start_work(self):
        self.label.setText("작업 중...")
        self.thread = WorkerThread()
        self.thread.progress.connect(self.update_progress)  # 진행률 신호 연결
        self.thread.finished.connect(self.work_finished)
        self.thread.start()

    def update_progress(self, value):
        self.label.setText(f"진행 중: {value}%")  # 진행률 업데이트

완성된 코드

class WorkerThread(QThread):
    finished = pyqtSignal()
    progress = pyqtSignal(int)

    def run(self):
        for i in range(10):
            time.sleep(1)  # 1초 대기
            self.progress.emit((i + 1) * 10)  # 진행률 신호 발송
        self.finished.emit()

class MainWindow(QWidget):
    # ... 기존 코드 생략 ...

    def start_work(self):
        self.label.setText("작업 중...")
        self.thread = WorkerThread()
        self.thread.progress.connect(self.update_progress)  # 진행률 신호 연결
        self.thread.finished.connect(self.work_finished)
        self.thread.start()

    def update_progress(self, value):
        self.label.setText(f"진행 중: {value}%")  # 진행률 업데이트

이제 사용자가 버튼을 클릭하면 레이블에 진행률이 표시되는 것을 볼 수 있습니다.

QThread 사용 시 주의사항

QThread를 사용할 때 주의해야 할 몇 가지 사항이 있습니다.

  • UI 업데이트는 항상 메인 스레드에서 수행해야 합니다. 백그라운드 스레드에서 UI를 수정하면 예기치 않은 동작이 발생할 수 있습니다.
  • 스레드 간 데이터 공유는 신호와 슬롯을 통해 수행해야 하며, 직접적인 속성 접근은 피해야 합니다.
  • 스레드의 생명주기를 명확하게 관리해야 합니다. 예를 들어, 스레드 종료 시 자원을 올바르게 해제해야 합니다.

QThread 종료 시 처리하기

스레드를 종료할 때는 종료 신호를 받아서 스레드가 안전하게 종료되도록 해야 합니다. 예를 들어, ‘stop’ 메서드를 정의하여 스레드를 안전하게 종료할 수 있습니다.

class WorkerThread(QThread):
    # ... 이전 코드 생략 ...

    def stop(self):
        self.terminate()  # 스레드 종료

이제 메인 애플리케이션에서 버튼을 추가하여 스레드를 종료할 수 있게 해보겠습니다.

class MainWindow(QWidget):
    def __init__(self):
        # ... 기존 코드 생략 ...

        self.stop_button = QPushButton("작업 중단")  # 중단 버튼 추가
        self.stop_button.clicked.connect(self.stop_work)

        self.layout.addWidget(self.stop_button)  # 레이아웃에 추가

    def stop_work(self):
        if hasattr(self, 'thread'):
            self.thread.stop()  # 스레드 중단

결론 및 추가 참고자료

이로써 PyQt에서 QThread를 사용한 멀티스레딩 처리에 대한 기본적인 내용을 살펴보았습니다. GUI 애플리케이션에서 멀티스레딩은 필수적인 요소이며, 적절한 사용법을 이해하고 있어야 합니다. QThread를 통해 업무를 효율적으로 처리하면서 사용자 경험을 향상시킬 수 있습니다.

참고자료

더 나아가, 복잡한 애플리케이션에서는 QThread 외에도 QThreadPool과 QRunnable을 이용하여 스레드 관리를 보다 용이하게 할 수 있습니다. 이를 통해 스레드를 재사용하거나 작업을 큐에 추가하는 방식으로 더욱 효율적인 멀티스레딩 환경을 구성할 수 있습니다.