If you use std::async to spawn a thread every time you have a new task, you risk exhausting the kernel's number of available threads for your process. A better way to run tasks concurrently is to use a thread pool--a small number of "worker threads" whose sole job is to run tasks as they are provided by the programmer. If there are more tasks than workers, the excess tasks are placed in a work queue. Whenever a worker finishes a task, it checks the work queue for new tasks.
This is a well-known idea, but has not yet been taken up into the standard library as of C++17. However, you can combine the ideas shown in this chapter to create your own production-quality thread pool. I'll walk through a simple one here; it's not "production quality" in terms of performance, but it is properly thread-safe and correct in all...