C++20 jthread stop_token 사용법, 스레드 취소를 어떻게 하나

C++20에서 추가된 std::jthread는 소멸 시 자동으로 join()을 호출하고, 내부에 std::stop_source를 들고 있어 협력적 취소(cooperative cancellation) 신호를 함께 전달합니다. 이 글은 cppreference를 기준으로 std::jthread stop_token을 이용해 스레드를 안전하게 취소하는 방법만 얇게 정리합니다.

atomic이나 RAII 자체에 대한 기본 설명은 다루지 않고, jthread의 자동 조인과 stop_token을 넘기고 확인하는 부분만 다룹니다.

jthread는 thread와 무엇이 다르나?

한 줄 답: 소멸 시 자동으로 join()을 호출하며, 내부에 std::stop_source를 내장하여 협력적 취소를 기본 지원합니다.

std::thread는 소멸 전에 join()이나 detach()를 명시적으로 호출하지 않으면 std::terminate가 호출되어 프로그램이 비정상 종료됩니다. 호출 경로가 여러 개이거나 예외가 섞이면 이 호출을 빠짐없이 챙기기가 쉽지 않습니다.

std::jthread는 소멸자에서 join()을 자동으로 호출합니다. 객체가 스코프를 벗어나는 순간 실행 중인 스레드를 안전하게 정리하므로, 명시적인 조인 호출을 잊어서 생기는 문제가 사라집니다.

또한 jthread 인스턴스는 내부에 std::stop_source 상태를 함께 들고 있습니다. 이 상태 덕분에 외부에서 request_stop()을 호출해 스레드에 취소를 요청할 수 있는 기반이 생성 시점부터 마련됩니다.

std::jthread worker([](std::stop_token st) {
    while (!st.stop_requested()) {
        // 작업 수행
    }
});
// worker가 스코프를 벗어나면 자동으로 join() 호출

stop_token은 어떻게 넘기나?

한 줄 답: 작업 함수(callable)의 첫 번째 매개변수로 std::stop_token을 받도록 선언하면 생성 시점에 알아서 전달됩니다.

취소는 스레드 외부에서 jthread 객체의 request_stop() 메서드를 호출하는 방식으로 시작합니다.

이 요청을 받으려면 스레드 작업 루틴(람다 또는 함수)의 첫 번째 인자로 std::stop_token을 받게끔 시그니처를 작성해야 합니다. jthread가 스레드를 실행할 때 이 토큰을 자동으로 주입합니다.

스레드 내부 작업 루프에서는 stop_token.stop_requested()를 주기적으로 폴링(poll)하고, true가 반환되면 루프를 안전하게 탈출(return)하도록 구현합니다.

void run(std::stop_token st) {
    while (!st.stop_requested()) {
        // 반복 작업
    }
    // 정리 후 반환
}

std::jthread t(run);
t.request_stop();

취소가 안 먹을 때 무엇을 점검하나?

한 줄 답: 스레드 내부 루프에서 stop_requested()를 주기적으로 검사하는지, 그리고 블로킹 대기에 condition_variable_any를 썼는지 확인합니다.

stop_token은 강제 종료(kill)가 아니라 협력적 취소입니다. 스레드 내부에서 상태를 확인하고 스스로 종료하지 않으면 취소 요청은 그대로 무시됩니다.

루프 안에 오래 걸리는 무거운 작업이 있다면, 그 사이사이에 stop_requested()를 확인하는 지점을 추가해야 합니다. 확인 지점이 드물면 취소 요청이 온 뒤에도 한참 뒤에야 반영됩니다.

블로킹 I/O나 조건 변수 대기 상태에 머물러 있는지도 점검 대상입니다. stop_token으로 인터럽트 가능한 대기(interruptible wait)를 구현하려면 기존 std::condition_variable 대신 std::condition_variable_any를 사용해야 합니다.

점검 항목확인할 내용
폴링 주기루프 안에서 stop_requested()를 충분히 자주 호출하는지 확인합니다.
블로킹 대기condition_variable_any로 인터럽트 가능한 대기를 구성했는지 확인합니다.
취소 방식강제 종료가 아닌 협력적 취소임을 전제로 스레드가 스스로 반환하는지 확인합니다.

즉시 반응이 필요하다면 std::stop_callback을 등록해 취소 요청 시점에 콜백을 바로 실행하는 방법도 있습니다.

출처