C++ 초급 - 추가 내용 (심화 학습) (4 - 비동기 프로그래밍 (std::thread, std::async))

2025. 2. 22. 18:43프로그래밍 언어/C++

📌 4. 비동기 프로그래밍 (std::thread, std::async)

비동기 프로그래밍은 멀티스레딩(multi-threading)과 비동기 작업을 통해 CPU를 효율적으로 사용하는 방법이다.
C++에서는 std::thread, std::async, std::mutex, std::future 등의 기능을 활용하여 멀티스레딩과 비동기 실행을 구현할 수 있다.


📌 4.1 C++에서의 멀티스레딩 개념과 필요성

🔹 (1) 멀티스레딩이란?

멀티스레딩(Multi-threading)은 여러 개의 스레드를 사용하여 동시에 여러 작업을 수행하는 기법이다.
이를 활용하면 CPU의 코어를 최대로 활용할 수 있어, 성능을 향상시킬 수 있다.

💡 멀티스레딩이 필요한 이유

  • 멀티코어 CPU 활용 극대화 → 여러 코어에서 동시에 실행 가능
  • UI 응답성 개선 → 메인 스레드가 블로킹되지 않도록 설계 가능
  • 네트워크 및 파일 I/O 효율화 → 비동기적으로 데이터 처리 가능

💡 예제: 단일 스레드 vs. 멀티스레드

#include <iostream>
#include <thread>

void task() {
    for (int i = 0; i < 5; ++i) {
        std::cout << "작업 실행 중..." << std::endl;
    }
}

int main() {
    std::thread t1(task);  // 멀티스레드 실행
    t1.join();  // 스레드 종료 대기

    std::cout << "메인 스레드 종료!" << std::endl;
    return 0;
}

🔹 설명

  • std::thread를 사용하여 별도의 스레드에서 task() 실행
  • t1.join() → 메인 스레드가 작업이 끝날 때까지 대기

📌 4.2 std::thread를 활용한 멀티스레딩 기초

🔹 (1) std::thread를 사용한 스레드 실행

💡 예제: 기본적인 std::thread 사용법

#include <iostream>
#include <thread>

void printMessage() {
    std::cout << "별도의 스레드에서 실행 중!" << std::endl;
}

int main() {
    std::thread t(printMessage);  // 새로운 스레드 실행
    t.join();  // 스레드 종료 대기

    std::cout << "메인 스레드 종료!" << std::endl;
    return 0;
}

🔹 출력 결과

별도의 스레드에서 실행 중!
메인 스레드 종료!

🔹 (2) join() vs. detach()

함수 설명
join() 스레드가 종료될 때까지 기다림 (Blocking)
detach() 스레드를 백그라운드에서 실행하고 제어 해제

💡 예제: detach()를 사용한 백그라운드 스레드

#include <iostream>
#include <thread>

void backgroundTask() {
    std::this_thread::sleep_for(std::chrono::seconds(2));
    std::cout << "백그라운드 스레드 완료!" << std::endl;
}

int main() {
    std::thread t(backgroundTask);
    t.detach();  // 백그라운드에서 실행됨

    std::cout << "메인 스레드 종료!" << std::endl;
    return 0;
}

🔹 출력 결과

메인 스레드 종료!
(2초 후) 백그라운드 스레드 완료!

📌 4.3 스레드 동기화 (std::mutex, std::lock_guard)

멀티스레딩 환경에서는 여러 스레드가 동시에 같은 데이터에 접근할 경우, 데이터 충돌(경쟁 상태, race condition)이 발생할 수 있다.
이를 방지하기 위해 std::mutex(뮤텍스)를 사용하여 한 번에 하나의 스레드만 특정 코드 블록을 실행하도록 설정할 수 있다.

🔹 (1) std::mutex를 사용한 동기화

💡 예제: std::mutex를 활용한 데이터 보호

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

std::mutex mtx;  // 뮤텍스 선언
int counter = 0;

void increase() {
    for (int i = 0; i < 10000; ++i) {
        mtx.lock();  // 뮤텍스 잠금
        ++counter;
        mtx.unlock();  // 뮤텍스 해제
    }
}

int main() {
    std::thread t1(increase);
    std::thread t2(increase);

    t1.join();
    t2.join();

    std::cout << "최종 카운터 값: " << counter << std::endl;
    return 0;
}

🔹 (2) std::lock_guard를 활용한 자동 잠금 관리

💡 예제: std::lock_guard를 활용한 안전한 동기화

void increase() {
    for (int i = 0; i < 10000; ++i) {
        std::lock_guard<std::mutex> lock(mtx);  // 뮤텍스 자동 해제
        ++counter;
    }
}

🔹 설명

  • std::lock_guard<std::mutex> lock(mtx); → 블록을 벗어나면 자동으로 unlock() 호출됨

📌 4.4 비동기 실행 (std::async, std::future) 개념

멀티스레딩 외에도, 비동기 실행(std::async)을 사용하면 함수 실행 결과를 비동기적으로 받을 수 있다.

💡 예제: std::async를 사용한 비동기 함수 실행

#include <iostream>
#include <future>

int computeSquare(int x) {
    return x * x;
}

int main() {
    std::future<int> result = std::async(std::launch::async, computeSquare, 5);

    std::cout << "결과: " << result.get() << std::endl;  // 결과 대기
    return 0;
}

🔹 설명

  • std::async(std::launch::async, 함수, 인자) → 함수를 비동기적으로 실행
  • .get()을 호출하면 결과를 기다림 (필요할 때까지 실행이 블로킹되지 않음)

📌 4.5 C++20의 std::jthread와 자동 종료 기능

C++20에서는 std::thread를 개선한 std::jthread(joining thread)가 도입되었다.

  • std::jthread는 소멸될 때 자동으로 join()을 호출하여 자원 누수를 방지할 수 있다.

💡 예제: std::jthread 사용

#include <iostream>
#include <thread>

void task() {
    std::cout << "스레드 실행 중..." << std::endl;
}

int main() {
    std::jthread t(task);  // 자동 join() 기능 포함
    return 0;
}

🔹 설명

  • std::jthread는 소멸될 때 자동으로 join() 호출하여 개발자가 명시적으로 join()을 호출할 필요가 없다.

📌 4.6 멀티스레딩에서 발생할 수 있는 문제점과 해결 방법

문제점 설명 해결 방법
데드락 (Deadlock) 두 개 이상의 스레드가 서로의 리소스를 기다리며 무한 대기 뮤텍스 사용 순서를 정해 교착 상태 방지
경쟁 상태 (Race Condition) 여러 스레드가 동시에 변수에 접근하여 예측 불가능한 결과 발생 std::mutex, std::lock_guard를 사용하여 동기화
자원 누수 (Resource Leak) join()을 호출하지 않아 스레드가 종료되지 않음 std::jthread를 사용하여 자동 join()

📌 5. 정리

개념  설명
std::thread 기본적인 멀티스레딩 지원
std::mutex 동기화를 위한 뮤텍스
std::async & std::future 비동기 실행 지원
std::jthread (C++20) 자동 join() 기능 제공