Qt - Thread(1)

구동엽
|2024. 7. 22. 15:15

Thread란 무엇인가

스레드는 프로그램에서 가장 작은 실행 단위로, 컴퓨터가 명령어를 수행하는 순서를 결정한다.

한 프로그램 내에서 동시에 여러 작업을 수행하게 만드는 역할을 한다.

 

프로세스 내에서 실행되는 각 스레드는 자신만의 레지스터 세트와 프로그램 카운터를 가지지만, 같은 포르세스 내의 다른 스레드와 힙 메모리, 전역 변수 등을 공유한다.

 

스레드는 프로세스보다 더 적은 리소스를 사용하여 생성하거나 제거할 수 있기 때문에, 동일한 프로세스 내에서 다양한 태스크를 빠르게 전환할 수 있다. 이는 사용자 인터페이스가 있는 응용 프로그램에서 중요한데, 이런 프로그램에서는 작업을 동시에 처맇거나, 한 작업이 완료될 동안 다른 작업을 계속 진행해야 할 때가 많다. 

 

#include <iostream>
#include <thread>

void function_1() {
    std::cout << "Function 1 has been called." << std::endl;
}

void function_2() {
    std::cout << "Function 2 has been called." << std::endl;
}

int main() {
    std::thread thread_1(function_1); // 첫 번째 스레드 생성
    std::thread thread_2(function_2); // 두 번째 스레드 생성

    thread_1.join(); // 첫 번째 스레드가 끝날 때까지 대기
    thread_2.join(); // 두 번째 스레드가 끝날 때까지 대기

    return 0;
}

이 코드는 두 개의 별도 함수, function_1과 function_2를 동시에 실행하는 두 개의 쓰레드를 생성한다.

각 함수는 간단한 메세지를 콘솔에 출력한다. join() 함수는 주 스레드가 해당 스레드가 종료될 때까지 기다리게 만든다.

 

이 예제를 통해 스레드를 생성하고, 그것들이 동시에 실행되는 방식을 확인할 수 있다.

하지만 실제 프로그램에서는 많은 수의 스레드가 동시에 실행되며, 이러한 스레드들은 공유 자원에 대한 동시 접근을 필요로 하기 때문에 복잡한 상황이 발생할 수 있다.

이러한 상황을 다루기 위해 우리는 스레드 동기화라는 개념을 도입해야 한다.

 

예를 들어, 두 개의 스레드가 동일한 메모리 위치에 쓰려고 할 때, 어떤 스레드가 먼저 접근해야 하는지 결정할 수 있는 방법이 필요하다.

이를 해결하기 위한 방법 중 하나가 뮤텍스(Mutex)이다.

뮤텍스는 공유 자원에 대한 동시 접근을 제어하는 데 사용되는 도구이다.

 

#include <iostream>
#include <thread>
#include <mutex>

std::mutex mtx; // 뮤텍스 객체 생성

void print_block(int n, char c) {
    mtx.lock(); // 뮤텍스 잠금
    for (int i=0; i<n; ++i) { 
      std::cout << c; 
    }
    std::cout << '\n';
    mtx.unlock(); // 뮤텍스 해제
}

int main() {
    std::thread th1(print_block, 50, '*');
    std::thread th2(print_block, 50, '$');

    th1.join();
    th2.join();

    return 0;
}

 

이 코드에서는 두 개의 스레드가 동시에 실행되고, 각 스레드는 print_block 함수를 호출하여 문자를 출력한다.

이 함수는 뮤텍스를 사용하여 출력 작업을 동기화한다.

즉, 한 번에 하나의 스레드만 출력을 수행할 수 있다.

 

Thread의 필요성

스레드는 동시에 여러 작업을 수행할 수 있도록 해주는 도구이다.

스레드를 사용하면 한 프로세스 내에서 여러 가지 연산을 동시에 실행할 수 있어, cpu 사용률을 극대화하고, 프로그램의 반응성을 향상시킬 수 있다.

이는 복잡한 계산이 필요한 프로그램, 사용자 인터페이스가 있는 프로그램, 네트워크 서버와 같은 상황에서 매우 유용하다.

 

예를 들어 복잡한 계산을 하는 프로그램에서 스레드를 사용하지 않는다면, CPU의 여러 코어 중 하나만 이용하게 된다.

이는 CPU의 나머지 코어가 FREE 상태로 남아있으면서, 프로그램이 느리게 실행된다.

반대로 복잡한 계산을 하는 프로그램을 스레드로 분할하면, 여러 코어에서 동시에 실행될 수 있으므로 전체 작업 시간이 줄어들고 CPU 사용률이 향상된다.

#include <iostream>
#include <thread>

// 복잡한 계산을 수행하는 함수
void complexCalculation() {
    // 복잡한 계산 수행...
}

int main() {
    std::thread t1(complexCalculation); // 첫 번째 스레드 생성
    std::thread t2(complexCalculation); // 두 번째 스레드 생성

    t1.join(); // 첫 번째 스레드가 끝날 때까지 기다림
    t2.join(); // 두 번째 스레드가 끝날 때까지 기다림

    return 0;
}

 

위의 코드에서는 복잡한 계산을 수행하는 complexCalculation 함수를 각각 다른 스레드에서 동시에 실행한다.

이를 통해 두 번의 계산을 동시에 처리할 수 있게 되어 전체 실행 시간을 줄일 수 있다.

 

또한, 사용자 인터페이스가 있는 프로그램에서는 사용자 입력을 받아 처리하는 동안에도 다른 작업을 계속해서 수행해야 할 수 있다. 이 경우에도 스레드를 사용하여 사용자 인터페이스와 백그라운드 작업을 동시에 처리할 수 있다. 

 

따라서, 스레드는 프로그램의 동시성을 높이고, CPU 사용률을 최적화하며, 더 나은 사용자 경험을 제공하는데 필요한 필수적인 도구이다. 이러한 이유로 많은 현대적인 프로그래밍 언어와 운영 체제는 스레드를 지원하고 있다. 그러나 스레드는 적절히 관리되지 않으면 복잡한 문제를 일으킬 수 있으므로, 스레드의 동작 방식과 이를 적절히 사용하는 방법을 이해하는 것이 중요다. 

 

Multi Thread의 장단점

장점

  • 동시성 : 멀티 스레드를 이용하면 CPU 사용률을 최대화하고, 블록된 스레드가 있는 동안 다른 스레드가 작업을 계속 할 수 있어 동시성을 확보할 수 있다.
  • 효율적인 자원 활용 : 스레드는 같은 프로세스 내에서 메모리와 자원을 공유하기 때문에, 자원을 효율적으로 활용할 수 있다.
  • 사용자 반응성 향상 : 멀티 스레드 애플리케이션에서는 하나의 스레드가 블록되거나, 지연되는 동아넹도 다른 스레드가 사용자와 상호작용을 계속 할 수 있다. 예를 들어, 웹 브라우저에서는 하나의 탭이 로딩되는 동안 다른 탭을 사용할 수 있다.

단점

  • 동기화 문제 : 멀티 스레드는 메모리와 자원을 공유하므로 스레드 간에 데이터를 공유하거나 접근할 때 동기화 문제가 발생 할 수 있다. 이를 해결하기 위해 뮤텍스, 세마포어 등의 동기화 기법을 사용해야 한다.
  • 디버깅 어려움 : 멀티 스레드 프로그램은 디버깅이 어렵다. 스레드는 실행 순서를 예측하기에는 불가능하여 동일한 입력에도 다른 출력을 내놓을 수 있다.
  • 설계와 구현 복잡도 구현 : 멀티 스레드 프로그램을 작성하려면 프로그램을 분할하고, 스레드 간의 통신을 관리하고, 동기화 이슈를 처리하는 등의 복잡한 작업이 필요하다.