C++ jthread | "Auto-Join Threads" Guide

C++ jthread | "Auto-Join Threads" Guide

이 글의 핵심

A practical guide to C++ jthread.

Entering

std::jthread in C++20 is an improved thread class that provides automatic join and abort mechanism. It is safe and convenient by following the RAII principles.


1. jthread basics

std::thread vs std::jthread```cpp

#include #include #include

using namespace std::chrono_literals;

// std::thread: manual join required void useThread() { std::thread t( { std::cout << “std::thread operation” << std::endl; });

t.join(); // essential! If not, call std::terminate }

// std::jthread: auto join void useJthread() { std::jthread jt( { std::cout << “std::jthread operation” << std::endl; });

// Automatically joined in destructor }

int main() { useThread(); useJthread(); }


| Features | std::thread | std::jthread |
|------|-------------|-------------|
| auto join | ❌ (requires manual) | ✅ (in destructor) |
| suspension mechanism | ❌ | ✅ (stop_token) |
| RAII | ❌ | ✅ |
| C++ version | C++11 | C++20 |

Key Concepts:
- RAII: Automatically clean up resources in destructor.
- Safety: Prevent terminate due to missing join()
- Convenience: No explicit join required

---

## 2. Basic use

### Simple example```cpp
#include <iostream>
#include <thread>
#include <chrono>

using namespace std::chrono_literals;

void simpleTask() {
std::cout << "start operation" << std::endl;
    std::this_thread::sleep_for(1s);
std::cout << "Operation completed" << std::endl;
}

int main() {
std::cout << "start main" << std::endl;
    
    {
        std::jthread t(simpleTask);
std::cout << "Thread created" << std::endl;
        // Auto-join when out of scope
    }
    
std::cout << "main end" << std::endl;
}
```### Passing parameters```cpp
#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);
    
// auto-joined
}
```---

## 3. Stopping with stop_token

### Default abort mechanism```cpp
#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 << "Working... (" << count++ << ")" << std::endl;
        std::this_thread::sleep_for(100ms);
    }
    
std::cout << "aborted" << std::endl;
}

int main() {
    std::jthread t(worker);
    
    std::this_thread::sleep_for(1s);
    
std::cout << "Abort request" << std::endl;
    t.request_stop();  // request to stop
    
    // Automatic join (in destructor)
}
```### Utilizing stop_token```cpp
#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()) {
// data processing
        processed++;
        
// Check for interruption periodically
        if (processed % 100 == 0) {
std::cout << "Processed data: " << processed << std::endl;
        }
        
        std::this_thread::sleep_for(10ms);
    }
    
std::cout << "Final processed: " << processed << std::endl;
}

int main() {
    std::jthread t(dataProcessor);
    
    std::this_thread::sleep_for(2s);
    t.request_stop();
}
```---

## 4. Practical example

### Example 1: RAII pattern```cpp
#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 << "task " << i << std::endl;
            std::this_thread::sleep_for(100ms);
        }
    });
    
    // Even if an exception occurs, workers are automatically joined.
    if (rand() % 2 == 0) {
throw std::runtime_error("An exception occurred!");
    }
    
std::cout << "normal end" << std::endl;
}

int main() {
    try {
        riskyOperation();
    } catch (const std::exception& e) {
std::cout << "Exception handling: " << e.what() << std::endl;
    }
    
std::cout << "main end" << std::endl;
}
```### Example 2: Managing multiple threads```cpp
#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 << "thread " << id << "working" << std::endl;
        std::this_thread::sleep_for(200ms);
    }
std::cout << "thread " << id << "end" << std::endl;
}

int main() {
    std::vector<std::jthread> threads;
    
// Create 5 threads
    for (int i = 0; i < 5; i++) {
        threads.emplace_back(workerTask, i);
    }
    
std::cout << "All threads running..." << std::endl;
    std::this_thread::sleep_for(2s);
    
std::cout << "Request to stop all threads" << std::endl;
    
// Request all threads to stop
    for (auto& t : threads) {
        t.request_stop();
    }
    
// Automatically join all threads when vector is destroyed
std::cout << "main end" << std::endl;
}
```### Example 3: Use with condition variables```cpp
#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);
        
        // wait supporting stop_token
        if (cv.wait(lock, stoken, []{ return !taskQueue.empty(); })) {
            int task = taskQueue.front();
            taskQueue.pop();
            lock.unlock();
            
std::cout << "Process: " << task << std::endl;
            std::this_thread::sleep_for(100ms);
        }
        
        if (stoken.stop_requested()) {
std::cout << "Exit worker" << std::endl;
            break;
        }
    }
}

int main() {
    std::jthread t(worker);
    
    // Add task
    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();  // Wake up a waiting thread
}
```---

## 5. Advanced use of stop_token

### stop_callback```cpp
#include <iostream>
#include <thread>
#include <chrono>

using namespace std::chrono_literals;

void worker(std::stop_token stoken) {
    // Register callback when requesting abort
    std::stop_callback callback(stoken,  {
std::cout << "Abort callback called!" << std::endl;
    });
    
    int count = 0;
    while (!stoken.stop_requested()) {
std::cout << "task " << count++ << std::endl;
        std::this_thread::sleep_for(200ms);
    }
}

int main() {
    std::jthread t(worker);
    
    std::this_thread::sleep_for(1s);
t.request_stop();  // callback is called immediately
}

stop_source

#include <iostream>
#include <thread>
#include <chrono>

using namespace std::chrono_literals;

void worker(std::stop_token stoken) {
    while (!stoken.stop_requested()) {
std::cout << "Working..." << 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();  // Request to stop with stop_source
}
```---

## 6. Frequently occurring problems

### Issue 1: Missing join (std::thread)```cpp
#include <thread>
#include <iostream>

// ❌ std::thread: terminate when join is missing
void badExample() {
    std::thread t( {
std::cout << "task" << std::endl;
    });
    
    // Destroyed without join() or detach()
    // → call std::terminate!
}

// ✅ std::thread: explicit join
void goodExample1() {
    std::thread t( {
std::cout << "task" << std::endl;
    });
    
    t.join();  // essential
}

// ✅ std::jthread: auto join
void goodExample2() {
    std::jthread t( {
std::cout << "task" << std::endl;
    });
    
    // Automatic join in destructor
}
```### Issue 2: Missing break check```cpp
#include <thread>
#include <iostream>
#include <chrono>

using namespace std::chrono_literals;

// ❌ No interruption check (infinite loop)
void badWorker(std::stop_token stoken) {
    while (true) {
std::cout << "Working..." << std::endl;
        std::this_thread::sleep_for(100ms);
// no stop_requested() check!
    }
}

// ✅ Periodically check for interruptions
void goodWorker(std::stop_token stoken) {
    while (!stoken.stop_requested()) {
std::cout << "Working..." << std::endl;
        std::this_thread::sleep_for(100ms);
    }
std::cout << "normal end" << std::endl;
}
```### Problem 3: Using detach```cpp
#include <thread>
#include <iostream>

int main() {
    std::jthread t( {
std::cout << "task" << std::endl;
    });
    
    // ❌ No automatic join after detachment
    t.detach();
    
    // Thread runs in background
    // When main terminates, threads may also be forcibly terminated.
}
```### Issue 4: Move Semantics```cpp
#include <thread>
#include <iostream>

int main() {
    std::jthread t1( {
std::cout << "task" << std::endl;
    });
    
// ✅ Moveable
    std::jthread t2 = std::move(t1);
    
// t1 is no longer valid
// t1.request_stop();  // undefined behavior
    
// t2 is valid
    t2.request_stop();
}
```---

## 7. Practical example: Background task manager```cpp
#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 << "Request to stop all operations" << std::endl;
        for (auto& t : threads) {
            t.request_stop();
        }
    }
    
    size_t activeCount() const {
        return threads.size();
    }
    
    ~TaskManager() {
std::cout << "TaskManager destruction (auto join)" << std::endl;
    }
    
private:
    std::vector<std::jthread> threads;
};

int main() {
    TaskManager manager;
    
// Task 1: Counter
    manager.addTask( {
        int count = 0;
        while (!stoken.stop_requested()) {
std::cout << "Counter: " << count++ << std::endl;
            std::this_thread::sleep_for(300ms);
        }
    });
    
//Task 2: Monitor
    manager.addTask( {
        while (!stoken.stop_requested()) {
std::cout << "Monitoring..." << std::endl;
            std::this_thread::sleep_for(500ms);
        }
    });
    
    // Task 3: Logger
    manager.addTask( {
        while (!stoken.stop_requested()) {
std::cout << "Log record" << std::endl;
            std::this_thread::sleep_for(1s);
        }
    });
    
std::cout << "Active task: " << manager.activeCount() << "count" << std::endl;
    
    std::this_thread::sleep_for(3s);
    manager.stopAll();
    
    std::this_thread::sleep_for(1s);
std::cout << "main end" << std::endl;
}
```---

## Cleanup

### Key takeaways

1. **Auto Join**: Automatically join() in destructor
2. **stop_token**: Cooperative stop mechanism
3. **RAII**: Exception safety guarantee
4. **stop_callback**: Execute callback when stopped
5. **std::thread replacement**: jthread recommended in C++20

### std::thread vs std::jthread

| Features | std::thread | std::jthread |
|------|-------------|-------------|
| join | manual (`join()`) | auto (destructor) |
| interruption | None | `stop_token` |
| exception safety | low | High (RAII) |
| Ease of use | Normal | High |
| C++ version | C++11 | C++20 |

### Practical tips

1. **Suspend Mechanism**
   - Check `stop_requested()` periodically
   - Add checkpoints in the middle of long tasks
   - Perform cleanup operations with `stop_callback`

2. **Performance**
   - Performance of jthread and thread is the same
   - Automatic join makes code concise
   - Reduce bugs with exception safety

3. **Migration**
   - Easily switch from `std::thread` → `std::jthread`
   - Stop support by adding stop_token parameter
   - Remove existing join() call

### Next steps

- [C++ stop_token](/blog/cpp-stop-token/)
- [C++ thread_local](/blog/cpp-thread-local/)
- [C++ Multithreading Basics](/blog/cpp-multithreading-basics/)

---

## Related articles

- [C++ stop_token | ](/blog/cpp-stop-token/)
- [C++ Barrier & Latch | ](/blog/cpp-barrier-latch/)
- [C++ Branch Prediction | ](/blog/cpp-branch-prediction/)
- [C++ Calendar & Timezone | ](/blog/cpp-calendar-timezone/)
- [Modern C++ (C++11~C++20) Core Grammar Cheat Sheet | A glance at frequently used items in the workplace](/blog/cpp-cheatsheet-02-modern-cpp-syntax/)