C++
multithreading
std::thread
thread management
concurrency

stdthread How to wait join for any of the given threads to complete?

Interview Questions practice on Codemia

Over 8,000 real interview questions from top companies, searchable by company and role.

Browse interview questions

Introduction

A common concurrency question in C++ is how to wait until any one thread completes, then continue processing. std::thread gives join, but join waits for one specific thread and there is no built-in wait-any primitive for a list of threads. To solve this, you coordinate completion with shared state, condition variables, or futures.

Why join Alone Is Not Enough

std::thread::join is a one-to-one operation. You choose one thread object and block until that thread finishes. If you have ten workers and only need the first result, calling join in a fixed order can block on a slow worker even when a fast worker already completed.

That is why production code usually separates two concerns:

  • completion signaling, which tells you that at least one task finished
  • thread cleanup, which joins or stops all remaining workers safely

Pattern 1: Signal First Completion with condition_variable

This pattern works with plain std::thread and keeps behavior explicit.

cpp
1#include <chrono>
2#include <condition_variable>
3#include <iostream>
4#include <mutex>
5#include <optional>
6#include <thread>
7#include <vector>
8
9struct SharedState {
10    std::mutex mtx;
11    std::condition_variable cv;
12    std::optional<int> firstFinished;
13};
14
15void worker(int id, int ms, SharedState& state) {
16    std::this_thread::sleep_for(std::chrono::milliseconds(ms));
17
18    {
19        std::lock_guard<std::mutex> lock(state.mtx);
20        if (!state.firstFinished.has_value()) {
21            state.firstFinished = id;
22        }
23    }
24
25    state.cv.notify_one();
26}
27
28int main() {
29    SharedState state;
30    std::vector<std::thread> threads;
31
32    threads.emplace_back(worker, 1, 800, std::ref(state));
33    threads.emplace_back(worker, 2, 250, std::ref(state));
34    threads.emplace_back(worker, 3, 500, std::ref(state));
35
36    {
37        std::unique_lock<std::mutex> lock(state.mtx);
38        state.cv.wait(lock, [&state] { return state.firstFinished.has_value(); });
39        std::cout << "First finished worker: " << *state.firstFinished << "\n";
40    }
41
42    for (auto& t : threads) {
43        if (t.joinable()) {
44            t.join();
45        }
46    }
47}

You get wait-any semantics for completion and still correctly join every thread before exit.

Pattern 2: Use Futures for Result-Driven Work

If each worker returns a value, std::future can make completion checks cleaner. You can poll futures with wait_for in a loop and react when one becomes ready.

cpp
1#include <chrono>
2#include <future>
3#include <iostream>
4#include <thread>
5#include <vector>
6
7int task(int id, int ms) {
8    std::this_thread::sleep_for(std::chrono::milliseconds(ms));
9    return id * 10;
10}
11
12int main() {
13    std::vector<std::future<int>> futures;
14    futures.push_back(std::async(std::launch::async, task, 1, 700));
15    futures.push_back(std::async(std::launch::async, task, 2, 200));
16    futures.push_back(std::async(std::launch::async, task, 3, 400));
17
18    bool done = false;
19    while (!done) {
20        for (size_t i = 0; i < futures.size(); ++i) {
21            if (futures[i].wait_for(std::chrono::milliseconds(0)) == std::future_status::ready) {
22                std::cout << "First ready future index: " << i << ", value: "
23                          << futures[i].get() << "\n";
24                done = true;
25                break;
26            }
27        }
28        std::this_thread::sleep_for(std::chrono::milliseconds(5));
29    }
30}

This pattern is simple for value-producing tasks, but polling intervals should be tuned to avoid unnecessary CPU wakeups.

Cleanup and Cancellation Strategy

Waiting for first completion does not automatically stop other threads. Decide what should happen next:

  • allow other workers to finish naturally
  • request cancellation with cooperative flags or stop tokens
  • keep workers running if they provide ongoing background service

In C++20, std::jthread plus stop_token can simplify cooperative cancellation and reduce cleanup bugs.

Performance and Design Considerations

The right pattern depends on workload. For short tasks, polling futures can be fine. For long tasks with unpredictable completion, event-driven signaling with condition variables is usually more efficient. If you need rich composition semantics, task systems or thread pools may be better than managing many raw thread objects.

Also avoid one thread per tiny task. Oversubscription can hurt performance due to context switching and cache disruption.

Common Pitfalls

A frequent bug is treating the first completed thread as if all work is complete and then exiting without joining others. That can terminate the program while workers still run. Another issue is writing shared completion flags without synchronization, which causes data races and undefined behavior. Polling futures in a tight loop without sleep can also burn CPU unnecessarily. Finally, calling get on a future more than once throws, so store result values immediately if you need to reuse them.

Summary

  • std::thread has no built-in wait-any operation across many threads.
  • Use condition_variable or futures to signal first completion.
  • Keep completion signaling separate from thread cleanup and joining.
  • Decide explicit behavior for remaining workers after first completion.
  • Prefer structured concurrency patterns when thread coordination grows complex.

Related reading
Free course
Beginner
7 lessons
2 hours
Tackling System Design Interview Problems

A short course that equips you with the skills to approach system design interviews methodically.

Start the free course
Basis Lab
No course covers this one yet

Basis Lab writes one for you from a sentence about what you want to be able to do, then teaches it and checks you understood. Your first course is free.

Build my course
Track what you have practised

A free account saves your progress, solutions and study plan across every problem on Codemia.

Interview Questions practice on Codemia

Over 8,000 real interview questions from top companies, searchable by company and role.

Browse interview questions