Post

std::async vs Thread Pool

std::async vs Thread Pool

std::async and Thread Pool


Prerequisites


1. What are std::async and Thread Pool

In C++, there are multiple ways to execute tasks concurrently:

  • std::async → simple, task-based concurrency
  • Thread Pool → reusable threads for high-performance workloads
1
2
std::async = create a thread per task
thread pool = reuse a fixed set of threads

2. std::async

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
#include <future>
#include <iostream>

int work(int x)
{
    return x * x;
}

int main()
{
    auto f1 = std::async(std::launch::async, work, 10);
    auto f2 = std::async(std::launch::async, work, 20);

    std::cout << f1.get() << "\n";
    std::cout << f2.get() << "\n";
}
1
2
3
- Each async call may create a new thread
- Task runs independently
- Result is returned via std::future

Use std::async when:

1
2
3
- Few tasks
- Simplicity matters
- No need for high performance
Pros
  • Easy to use
  • Automatic thread management
  • Built-in result handling (future)
Cons
  • Thread creation overhead
  • No reuse of threads
  • Poor scalability for many tasks

3. Thread Pool

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
#include <iostream>
#include <thread>
#include <queue>
#include <vector>
#include <functional>
#include <mutex>
#include <condition_variable>

class ThreadPool
{
public:
    ThreadPool(size_t n) : stop(false)
    {
        auto lmdThread = [this]()
        {
            auto lmdcondition = [this]() -> bool
            {
                return stop || !tasks.empty();
            };

            while (true)
            {
                bool bFinish = false;

                do
                {
                    std::function<void()> task;

                    {
                        std::unique_lock<std::mutex> lock(m);
                        cv.wait(lock, lmdcondition);

                        if (stop && tasks.empty())
                        {
                            bFinish = true;
                            break;
                        }

                        task = std::move(tasks.front());
                        tasks.pop();
                    }

                    task();
                } 
                while (false);

                if(bFinish)
                    break;
            }
        };

        for (size_t i = 0; i < n; i++)
            workers.emplace_back(lmdThread);
    }

    ~ThreadPool()
    {
        {
            std::lock_guard<std::mutex> lock(m);
            stop = true;
        }

        cv.notify_all();

        for (auto& worker : workers)
        {
            if (worker.joinable())
                worker.join();
        }
    }

    void enqueue(std::function<void()> task)
    {
        {
            std::lock_guard<std::mutex> lock(m);
            tasks.push(std::move(task));
        }

        cv.notify_one();
    }

private:
    std::vector<std::thread> workers;
    std::queue<std::function<void()>> tasks;
    std::mutex m;
    std::condition_variable cv;
    bool stop;
};

int main()
{
    ThreadPool pool(2);

    pool.enqueue([] { std::cout << "Task 1\n"; });
    pool.enqueue([] { std::cout << "Task 2\n"; });
    pool.enqueue([] { std::cout << "Task 3\n"; });

    std::this_thread::sleep_for(std::chrono::seconds(1));
}
1
2
3
- Threads are created once
- Tasks are queued
- Threads continuously fetch and execute tasks

Use Thread Pool when:

1
2
3
- Many small tasks
- Performance is critical
- Reusing threads is important
Pros
  • No repeated thread creation
  • Better performance for many tasks
  • Scales well
Cons
  • More complex to implement
  • Manual management required
  • No built-in result handling (unless extended)

4. Performance

Aspectstd::asyncThread Pool
Thread creationPer taskOnce
Reuse✔️
OverheadHigh (many tasks)Low
ScalabilityPoorGood
Ease of useEasyModerate

5. thread_local

1
2
std::async → thread recreated → thread_local reset
thread pool → thread reused → thread_local persists
This post is licensed under CC BY 4.0 by the author.