과거 C++98/03 시절, 멀티스레딩을 구현하기 위해서는 운영체제별 API(pthread, Win32 threads 등)를 직접 호출하거나, Boost Threads와 같은 서드파티 라이브러리에 의존해야 했습니다. 이는 코드 이식성을 저하시켰고, 스레드 생성, 종료, 동기화 관리가 번거롭게 이루어지는 경우가 많았습니다.
C++11 이후 표준 라이브러리에 std::thread, std::mutex, std::lock_guard, std::async 등 기본적인 멀티스레딩 기능이 도입되었고, C++20에서는 std::jthread와 중단 요청(stop token) 메커니즘이 추가되어 스레드 관리가 더 단순해졌습니다. 또한 C++20 코루틴(coroutine)을 활용하면 비동기 처리를 더 간결하고 직관적으로 구현할 수 있습니다.
관련 참고 자료:
과거: 운영체제별 스레드와 수동적 자원 관리
C++98/03 시절 멀티스레딩은 표준으로 지원되지 않아, 플랫폼별 API를 직접 호출하거나 Boost Threads 같은 서드파티 라이브러리에 의존해야 했습니다. 이 경우 스레드를 생성, 조인(join), 종료할 때 발생하는 예외 상황을 직접 처리하고, 안전한 종료를 보장하기 위해 많은 주의를 기울여야 했습니다.
// 가상 예제: POSIX pthread 사용(플랫폼 의존)
#include <pthread.h>
#include <iostream>
void* thread_func(void* arg) {
std::cout << "Hello from pthread!\n";
return nullptr;
}
int main() {
pthread_t tid;
pthread_create(&tid, nullptr, thread_func, nullptr);
pthread_join(tid, nullptr); // 수동 조인 필요
return 0;
}
위 예제처럼 스레드 생성, 조인 시 플랫폼 별 함수를 호출하고, 에러 처리 로직도 직접 다루어야 했습니다.
현재: std::jthread와 코루틴
std::jthread: RAII 기반 스레드 관리
C++11에서 도입된 std::thread는 플랫폼 독립적인 스레드 인터페이스를 제공하지만, 스레드가 범위를 벗어날 때 자동으로 조인(join)하지는 않았습니다. C++20의 std::jthread는 RAII를 통해 스레드 종료를 자동 처리하며, 중단 요청(stop request) 메커니즘을 지원해 스레드 종료를 안전하고 간단하게 관리할 수 있습니다.
#include <iostream>
#include <thread>
#include <stop_token>
void worker(std::stop_token st) {
while (!st.stop_requested()) {
std::cout << "Working...\n";
std::this_thread::sleep_for(std::chrono::milliseconds(100));
}
std::cout << "Stopped!\n";
}
int main() {
std::jthread jt(worker);
std::this_thread::sleep_for(std::chrono::milliseconds(500));
// 중단 요청
jt.request_stop();
// jt 소멸 시 자동으로 조인
return 0;
}
std::jthread는 범위를 벗어날 때 자동으로 조인하므로 스레드 관리에 대한 부담이 줄어듭니다. 또한 request_stop()로 중단을 알리고, 작업 함수는 stop_requested()를 통해 안전하게 종료를 감지할 수 있습니다.
코루틴: 비동기 코드를 동기적 흐름처럼 작성
C++20 코루틴을 사용하면 비동기 처리를 더 직관적으로 표현할 수 있습니다. 기존에는 콜백이나 미래(std::future) 기반으로 비동기 코드를 작성해야 했지만, 코루틴을 통해 "비동기 함수 호출"을 마치 동기 함수처럼 기술할 수 있습니다.
#include <iostream>
#include <coroutine>
#include <chrono>
#include <thread>
struct my_task {
struct promise_type {
my_task get_return_object() { return {}; }
std::suspend_never initial_suspend() { return {}; }
std::suspend_never final_suspend() noexcept { return {}; }
void return_void() {}
void unhandled_exception() {}
};
};
my_task async_print() {
std::cout << "Start\n";
std::this_thread::sleep_for(std::chrono::milliseconds(500));
std::cout << "End\n";
co_return;
}
int main() {
// 이 예제는 단순한 코루틴 예시이며, 비동기 I/O나 이벤트 루프를 통해 더 실용적으로 활용 가능
async_print();
return 0;
}
위 예제는 단순한 예제지만, 코루틴을 활용하면 각종 비동기 I/O, 타이머, 이벤트 기반 로직을 동기 코드에 가깝게 표현할 수 있습니다. 이를 통해 비동기 로직이 복잡하게 얽힌 콜백 지옥(callback hell)을 탈출하고, 유지보수하기 쉬운 코드를 작성할 수 있습니다.
왜 이런 변화가 필요한가?
- 코드 이식성 및 단순화
표준 라이브러리 기반 스레드(std::thread, std::jthread)를 사용하면 운영체제별 API를 직접 호출할 필요가 없고, RAII를 통해 스레드 종료 관리가 단순해집니다. - 안전한 중단 메커니즘
std::jthread와 중단 토큰(stop token)을 활용하면 스레드를 우아하게 종료하는 로직을 간단히 구현할 수 있습니다. - 비동기 코드 가독성 향상
C++20 코루틴을 통해 비동기 코드를 동기적으로 기술할 수 있어, 복잡한 상태 관리나 콜백 체인을 줄이고, 읽기 쉽고 유지보수하기 좋은 비동기 코드를 작성할 수 있습니다.
'개발 이야기 > C++' 카테고리의 다른 글
[모던 C++ #11] 모던 C++으로 가는 여정의 마침표: 이제 어떤 길을 갈까? (0) | 2024.12.14 |
---|---|
[모던 C++ #10] 직접 구현한 알고리즘 대신 C++20 Ranges와 병렬 알고리즘으로! (0) | 2024.12.14 |
[모던 C++ #8] 구식 function object와 std::bind를 버리고 람다와 std::function로! (0) | 2024.12.14 |
[모던 C++ #7] SFINAE 트릭을 떠나 Concepts로! (0) | 2024.12.14 |
[모던 C++ #6] printf에서 벗어나 std::format으로! (0) | 2024.12.14 |