콘텐츠로 이동

C++20 jthread와 stop_token — 협력적 스레드 취소

C++20은 std::thread의 두 가지 문제를 해결한 std::jthread를 도입했습니다.

  1. 자동 join: 소멸자에서 자동으로 join()을 호출합니다.
  2. 협력적 취소: std::stop_token으로 스레드에 취소 요청을 보낼 수 있습니다.

#include <thread>
#include <chrono>
#include <iostream>
void task() {
std::this_thread::sleep_for(std::chrono::seconds(1));
std::cout << "완료\n";
}
int main() {
std::jthread t(task);
// 소멸자에서 자동 join — join() 호출 불필요
}

#include <thread>
#include <chrono>
#include <iostream>
void worker(std::stop_token st) {
while (!st.stop_requested()) {
std::cout << "작업 중...\n";
std::this_thread::sleep_for(std::chrono::milliseconds(200));
}
std::cout << "취소 요청 수신, 종료\n";
}
int main() {
std::jthread t(worker);
std::this_thread::sleep_for(std::chrono::seconds(1));
t.request_stop(); // 취소 요청 전송
// 소멸자에서 자동 join
}

3. stop_callback — 취소 시 정리 작업

섹션 제목: “3. stop_callback — 취소 시 정리 작업”
#include <thread>
#include <mutex>
#include <condition_variable>
std::mutex mtx;
std::condition_variable_any cv;
void worker(std::stop_token st) {
// 취소 요청 시 condition_variable을 깨움
std::stop_callback cb(st, [&] { cv.notify_all(); });
std::unique_lock lock(mtx);
cv.wait(lock, st, [] { return false; }); // stop_token 인식 대기
std::cout << "대기 해제\n";
}
int main() {
std::jthread t(worker);
std::this_thread::sleep_for(std::chrono::milliseconds(500));
t.request_stop();
}

#include <thread>
#include <vector>
#include <iostream>
int main() {
std::stop_source src;
std::vector<std::jthread> workers;
for (int i = 0; i < 4; ++i) {
workers.emplace_back([i](std::stop_token st) {
while (!st.stop_requested()) {
std::this_thread::sleep_for(std::chrono::milliseconds(100));
}
std::cout << "워커 " << i << " 종료\n";
}, src.get_token()); // 공유 토큰 전달
}
std::this_thread::sleep_for(std::chrono::milliseconds(500));
src.request_stop(); // 모든 워커에 취소 요청
}

항목std::threadstd::jthread
소멸자 동작std::terminate() 호출자동 join()
취소 지원없음stop_token
join 필요필수불필요
사용 가능 버전C++11C++20

6. 실전 패턴: 백그라운드 서비스

섹션 제목: “6. 실전 패턴: 백그라운드 서비스”
class BackgroundService {
std::jthread thread_;
void run(std::stop_token st) {
while (!st.stop_requested()) {
process();
std::this_thread::sleep_for(std::chrono::seconds(1));
}
}
void process() { /* 실제 작업 */ }
public:
void start() {
thread_ = std::jthread([this](std::stop_token st) { run(st); });
}
void stop() { thread_.request_stop(); }
};


#include <thread>
#include <mutex>
#include <condition_variable>
#include <chrono>
// stop_token + 타임아웃 조합
void worker_with_timeout(std::stop_token st) {
std::mutex mtx;
std::condition_variable_any cv;
std::stop_callback cb(st, [&cv] { cv.notify_all(); });
std::unique_lock lock(mtx);
// 최대 5초 대기, 취소 요청 오면 즉시 종료
cv.wait_for(lock, st, std::chrono::seconds(5),
[] { return false; }); // 조건 없이 대기
if (st.stop_requested())
std::cout << "취소됨\n";
else
std::cout << "타임아웃\n";
}
int main() {
std::jthread t(worker_with_timeout);
std::this_thread::sleep_for(std::chrono::milliseconds(200));
t.request_stop(); // 5초 전에 취소
}

#include <thread>
#include <iostream>
void worker(std::stop_token st) {
// 여러 콜백을 순서대로 등록 (역순으로 호출됨)
std::stop_callback cb1(st, [] { std::cout << "정리 1: DB 연결 닫기\n"; });
std::stop_callback cb2(st, [] { std::cout << "정리 2: 소켓 닫기\n"; });
std::stop_callback cb3(st, [] { std::cout << "정리 3: 로그 플러시\n"; });
// cb3 소멸 시 자동 해제됨 (RAII)
while (!st.stop_requested()) {
std::this_thread::sleep_for(std::chrono::milliseconds(100));
}
}
// request_stop() 시 콜백 호출 순서: cb3 → cb2 → cb1 (등록 역순)

std::jthread는 RAII 기반 자동 join과 협력적 취소를 제공해 스레드 생명주기 관리를 크게 단순화합니다. stop_callback으로 취소 시 정리 작업을 등록할 수 있고, condition_variable_any::waitstop_token을 직접 전달해 블로킹 대기를 즉시 중단할 수 있습니다. 새 코드에서는 std::thread 대신 std::jthread를 기본으로 사용하세요.