[2026] C++ jthread | 자동 조인 스레드 가이드

[2026] C++ jthread | 자동 조인 스레드 가이드

이 글의 핵심

C++ jthread의 C++, jthread, 자동, 1.

들어가며

C++20의 std::jthread자동 조인중단 메커니즘을 제공하는 개선된 스레드 클래스입니다. RAII 원칙을 따라 안전하고 편리합니다.

실무에서 마주한 현실

개발을 배울 때는 모든 게 깔끔하고 이론적입니다. 하지만 실무는 다릅니다. 레거시 코드와 씨름하고, 급한 일정에 쫓기고, 예상치 못한 버그와 마주합니다. 이 글에서 다루는 내용도 처음엔 이론으로 배웠지만, 실제 프로젝트에 적용하면서 “아, 이래서 이렇게 설계하는구나” 하고 깨달은 것들입니다. 특히 기억에 남는 건 첫 프로젝트에서 겪은 시행착오입니다. 책에서 배운 대로 했는데 왜 안 되는지 몰라 며칠을 헤맸죠. 결국 선배 개발자의 코드 리뷰를 통해 문제를 발견했고, 그 과정에서 많은 걸 배웠습니다. 이 글에서는 이론뿐 아니라 실전에서 마주칠 수 있는 함정들과 해결 방법을 함께 다루겠습니다.

1. jthread 기본

std::thread vs std::jthread

다음은 cpp를 활용한 상세한 구현 코드입니다. 필요한 모듈을 import하고. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.

#include <iostream>
#include <thread>
#include <chrono>
using namespace std::chrono_literals;
// std::thread: 수동 조인 필요
void useThread() {
    std::thread t( {
        std::cout << "std::thread 작업" << std::endl;
    });
    
    t.join();  // 필수! 없으면 std::terminate 호출
}
// std::jthread: 자동 조인
void useJthread() {
    std::jthread jt( {
        std::cout << "std::jthread 작업" << std::endl;
    });
    
    // 소멸자에서 자동으로 조인됨
}
int main() {
    useThread();
    useJthread();
}

비교표

특징std::threadstd::jthread
자동 조인❌ (수동 필요)✅ (소멸자에서)
중단 메커니즘✅ (stop_token)
RAII
C++ 버전C++11C++20
핵심 개념:
  • RAII: 소멸자에서 자동으로 리소스 정리
  • 안전성: join() 누락으로 인한 terminate 방지
  • 편의성: 명시적 조인 불필요

2. 기본 사용

간단한 예제

다음은 cpp를 활용한 상세한 구현 코드입니다. 필요한 모듈을 import하고, 반복문으로 데이터를 처리합니다. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.

#include <iostream>
#include <thread>
#include <chrono>
using namespace std::chrono_literals;
void simpleTask() {
    std::cout << "작업 시작" << std::endl;
    std::this_thread::sleep_for(1s);
    std::cout << "작업 완료" << std::endl;
}
int main() {
    std::cout << "메인 시작" << std::endl;
    
    {
        std::jthread t(simpleTask);
        std::cout << "스레드 생성됨" << std::endl;
        // 스코프를 벗어나면 자동 조인
    }
    
    std::cout << "메인 종료" << std::endl;
}

매개변수 전달

다음은 cpp를 활용한 상세한 구현 코드입니다. 필요한 모듈을 import하고, 반복문으로 데이터를 처리합니다. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.

#include <iostream>
#include <thread>
void printNumbers(int start, int end) {
    for (int i = start; i <= end; i++) {
        std::cout << i << " ";
    }
    std::cout << std::endl;
}
int main() {
    std::jthread t1(printNumbers, 1, 5);
    std::jthread t2(printNumbers, 10, 15);
    
    // 자동 조인됨
}

3. stop_token으로 중단하기

기본 중단 메커니즘

다음은 cpp를 활용한 상세한 구현 코드입니다. 필요한 모듈을 import하고, 반복문으로 데이터를 처리합니다. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.

#include <iostream>
#include <thread>
#include <chrono>
using namespace std::chrono_literals;
void worker(std::stop_token stoken) {
    int count = 0;
    
    while (!stoken.stop_requested()) {
        std::cout << "작업 중....(" << count++ << ")" << std::endl;
        std::this_thread::sleep_for(100ms);
    }
    
    std::cout << "중단됨" << std::endl;
}
int main() {
    std::jthread t(worker);
    
    std::this_thread::sleep_for(1s);
    
    std::cout << "중단 요청" << std::endl;
    t.request_stop();  // 중단 요청
    
    // 자동 조인 (소멸자에서)
}

stop_token 활용

다음은 cpp를 활용한 상세한 구현 코드입니다. 필요한 모듈을 import하고, 반복문으로 데이터를 처리합니다, 조건문으로 분기 처리를 수행합니다. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.

#include <iostream>
#include <thread>
#include <chrono>
#include <atomic>
using namespace std::chrono_literals;
void dataProcessor(std::stop_token stoken) {
    std::atomic<int> processed{0};
    
    while (!stoken.stop_requested()) {
        // 데이터 처리
        processed++;
        
        // 주기적으로 중단 확인
        if (processed % 100 == 0) {
            std::cout << "처리된 데이터: " << processed << std::endl;
        }
        
        std::this_thread::sleep_for(10ms);
    }
    
    std::cout << "최종 처리량: " << processed << std::endl;
}
int main() {
    std::jthread t(dataProcessor);
    
    std::this_thread::sleep_for(2s);
    t.request_stop();
}

4. 실전 예제

예제 1: RAII 패턴

다음은 cpp를 활용한 상세한 구현 코드입니다. 필요한 모듈을 import하고, 비동기 처리를 통해 효율적으로 작업을 수행합니다, 에러 처리를 통해 안정성을 확보합니다, 반복문으로 데이터를 처리합니다, 조건문으로 분기 처리를 수행합니다. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.

#include <iostream>
#include <thread>
#include <stdexcept>
#include <chrono>
using namespace std::chrono_literals;
void riskyOperation() {
    std::jthread worker( {
        for (int i = 0; i < 10; i++) {
            std::cout << "작업 " << i << std::endl;
            std::this_thread::sleep_for(100ms);
        }
    });
    
    // 예외가 발생해도 worker는 자동으로 조인됨
    if (rand() % 2 == 0) {
        throw std::runtime_error("예외 발생!");
    }
    
    std::cout << "정상 종료" << std::endl;
}
int main() {
    try {
        riskyOperation();
    } catch (const std::exception& e) {
        std::cout << "예외 처리: " << e.what() << std::endl;
    }
    
    std::cout << "메인 종료" << std::endl;
}

예제 2: 여러 스레드 관리

다음은 cpp를 활용한 상세한 구현 코드입니다. 필요한 모듈을 import하고, 반복문으로 데이터를 처리합니다. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.

#include <iostream>
#include <thread>
#include <vector>
#include <chrono>
using namespace std::chrono_literals;
void workerTask(int id, std::stop_token stoken) {
    while (!stoken.stop_requested()) {
        std::cout << "스레드 " << id << " 작업 중" << std::endl;
        std::this_thread::sleep_for(200ms);
    }
    std::cout << "스레드 " << id << " 종료" << std::endl;
}
int main() {
    std::vector<std::jthread> threads;
    
    // 5개 스레드 생성
    for (int i = 0; i < 5; i++) {
        threads.emplace_back(workerTask, i);
    }
    
    std::cout << "모든 스레드 실행 중..." << std::endl;
    std::this_thread::sleep_for(2s);
    
    std::cout << "모든 스레드 중단 요청" << std::endl;
    
    // 모든 스레드에 중단 요청
    for (auto& t : threads) {
        t.request_stop();
    }
    
    // 벡터 소멸 시 자동으로 모든 스레드 조인
    std::cout << "메인 종료" << std::endl;
}

예제 3: 조건 변수와 함께 사용

다음은 cpp를 활용한 상세한 구현 코드입니다. 필요한 모듈을 import하고, 반복문으로 데이터를 처리합니다, 조건문으로 분기 처리를 수행합니다. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.

#include <iostream>
#include <thread>
#include <mutex>
#include <condition_variable>
#include <queue>
#include <chrono>
using namespace std::chrono_literals;
std::mutex mtx;
std::condition_variable_any cv;
std::queue<int> taskQueue;
void worker(std::stop_token stoken) {
    while (true) {
        std::unique_lock<std::mutex> lock(mtx);
        
        // stop_token을 지원하는 wait
        if (cv.wait(lock, stoken, []{ return !taskQueue.empty(); })) {
            int task = taskQueue.front();
            taskQueue.pop();
            lock.unlock();
            
            std::cout << "처리: " << task << std::endl;
            std::this_thread::sleep_for(100ms);
        }
        
        if (stoken.stop_requested()) {
            std::cout << "워커 종료" << std::endl;
            break;
        }
    }
}
int main() {
    std::jthread t(worker);
    
    // 작업 추가
    for (int i = 0; i < 10; i++) {
        {
            std::lock_guard<std::mutex> lock(mtx);
            taskQueue.push(i);
        }
        cv.notify_one();
        std::this_thread::sleep_for(50ms);
    }
    
    std::this_thread::sleep_for(2s);
    t.request_stop();
    cv.notify_one();  // 대기 중인 스레드 깨우기
}

5. stop_token 고급 활용

stop_callback

다음은 cpp를 활용한 상세한 구현 코드입니다. 필요한 모듈을 import하고, 반복문으로 데이터를 처리합니다. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.

#include <iostream>
#include <thread>
#include <chrono>
using namespace std::chrono_literals;
void worker(std::stop_token stoken) {
    // 중단 요청 시 콜백 등록
    std::stop_callback callback(stoken,  {
        std::cout << "중단 콜백 호출됨!" << std::endl;
    });
    
    int count = 0;
    while (!stoken.stop_requested()) {
        std::cout << "작업 " << count++ << std::endl;
        std::this_thread::sleep_for(200ms);
    }
}
int main() {
    std::jthread t(worker);
    
    std::this_thread::sleep_for(1s);
    t.request_stop();  // 콜백이 즉시 호출됨
}

stop_source

다음은 cpp를 활용한 상세한 구현 코드입니다. 필요한 모듈을 import하고, 반복문으로 데이터를 처리합니다. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.

#include <iostream>
#include <thread>
#include <chrono>
using namespace std::chrono_literals;
void worker(std::stop_token stoken) {
    while (!stoken.stop_requested()) {
        std::cout << "작업 중..." << std::endl;
        std::this_thread::sleep_for(200ms);
    }
}
int main() {
    std::stop_source ssource;
    std::stop_token stoken = ssource.get_token();
    
    std::jthread t(worker, stoken);
    
    std::this_thread::sleep_for(1s);
    ssource.request_stop();  // stop_source로 중단 요청
}

6. 자주 발생하는 문제

문제 1: 조인 누락 (std::thread)

다음은 cpp를 활용한 상세한 구현 코드입니다. 필요한 모듈을 import하고. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.

#include <thread>
#include <iostream>
// ❌ std::thread: 조인 누락 시 terminate
void badExample() {
    std::thread t( {
        std::cout << "작업" << std::endl;
    });
    
    // join()이나 detach() 없이 소멸
    // → std::terminate 호출!
}
// ✅ std::thread: 명시적 조인
void goodExample1() {
    std::thread t( {
        std::cout << "작업" << std::endl;
    });
    
    t.join();  // 필수
}
// ✅ std::jthread: 자동 조인
void goodExample2() {
    std::jthread t( {
        std::cout << "작업" << std::endl;
    });
    
    // 소멸자에서 자동 조인
}

문제 2: 중단 체크 누락

다음은 cpp를 활용한 상세한 구현 코드입니다. 필요한 모듈을 import하고, 반복문으로 데이터를 처리합니다. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.

#include <thread>
#include <iostream>
#include <chrono>
using namespace std::chrono_literals;
// ❌ 중단 체크 안함 (무한 루프)
void badWorker(std::stop_token stoken) {
    while (true) {
        std::cout << "작업 중..." << std::endl;
        std::this_thread::sleep_for(100ms);
        // stop_requested() 체크 없음!
    }
}
// ✅ 주기적으로 중단 체크
void goodWorker(std::stop_token stoken) {
    while (!stoken.stop_requested()) {
        std::cout << "작업 중..." << std::endl;
        std::this_thread::sleep_for(100ms);
    }
    std::cout << "정상 종료" << std::endl;
}

문제 3: detach 사용

아래 코드는 cpp를 사용한 구현 예제입니다. 필요한 모듈을 import하고. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.

#include <thread>
#include <iostream>
int main() {
    std::jthread t( {
        std::cout << "작업" << std::endl;
    });
    
    // ❌ detach 후에는 자동 조인 안됨
    t.detach();
    
    // 스레드가 백그라운드에서 실행
    // 메인이 종료되면 스레드도 강제 종료될 수 있음
}

문제 4: 이동 의미론

다음은 cpp를 활용한 상세한 구현 코드입니다. 필요한 모듈을 import하고. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.

#include <thread>
#include <iostream>
int main() {
    std::jthread t1( {
        std::cout << "작업" << std::endl;
    });
    
    // ✅ 이동 가능
    std::jthread t2 = std::move(t1);
    
    // t1은 더 이상 유효하지 않음
    // t1.request_stop();  // 정의되지 않은 동작
    
    // t2는 유효
    t2.request_stop();
}

7. 실전 예제: 백그라운드 작업 관리자

다음은 cpp를 활용한 상세한 구현 코드입니다. 필요한 모듈을 import하고, 클래스를 정의하여 데이터와 기능을 캡슐화하며, 반복문으로 데이터를 처리합니다. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.

#include <iostream>
#include <thread>
#include <vector>
#include <functional>
#include <chrono>
using namespace std::chrono_literals;
class TaskManager {
public:
    using Task = std::function<void(std::stop_token)>;
    
    void addTask(Task task) {
        threads.emplace_back(task);
    }
    
    void stopAll() {
        std::cout << "모든 작업 중단 요청" << std::endl;
        for (auto& t : threads) {
            t.request_stop();
        }
    }
    
    size_t activeCount() const {
        return threads.size();
    }
    
    ~TaskManager() {
        std::cout << "TaskManager 소멸 (자동 조인)" << std::endl;
    }
    
private:
    std::vector<std::jthread> threads;
};
int main() {
    TaskManager manager;
    
    // 작업 1: 카운터
    manager.addTask( {
        int count = 0;
        while (!stoken.stop_requested()) {
            std::cout << "카운터: " << count++ << std::endl;
            std::this_thread::sleep_for(300ms);
        }
    });
    
    // 작업 2: 모니터
    manager.addTask( {
        while (!stoken.stop_requested()) {
            std::cout << "모니터링..." << std::endl;
            std::this_thread::sleep_for(500ms);
        }
    });
    
    // 작업 3: 로거
    manager.addTask( {
        while (!stoken.stop_requested()) {
            std::cout << "로그 기록" << std::endl;
            std::this_thread::sleep_for(1s);
        }
    });
    
    std::cout << "활성 작업: " << manager.activeCount() << "개" << std::endl;
    
    std::this_thread::sleep_for(3s);
    manager.stopAll();
    
    std::this_thread::sleep_for(1s);
    std::cout << "메인 종료" << std::endl;
}

정리

핵심 요약

  1. 자동 조인: 소멸자에서 자동으로 join()
  2. stop_token: 협력적 중단 메커니즘
  3. RAII: 예외 안전성 보장
  4. stop_callback: 중단 시 콜백 실행
  5. std::thread 대체: C++20에서 jthread 권장

std::thread vs std::jthread

특징std::threadstd::jthread
조인수동 (join())자동 (소멸자)
중단없음stop_token
예외 안전성낮음높음 (RAII)
사용 편의성보통높음
C++ 버전C++11C++20

실전 팁

  1. 중단 메커니즘
    • 주기적으로 stop_requested() 확인
    • 긴 작업은 중간에 체크 포인트 추가
    • stop_callback으로 정리 작업 수행
  2. 성능
    • jthread와 thread의 성능은 동일
    • 자동 조인으로 코드가 간결해짐
    • 예외 안전성으로 버그 감소
  3. 마이그레이션
    • std::threadstd::jthread로 쉽게 전환
    • stop_token 매개변수 추가로 중단 지원
    • 기존 join() 호출 제거

다음 단계


관련 글

... 996 lines not shown ... Token usage: 63706/1000000; 936294 remaining Start-Sleep -Seconds 3