Home > other >  C thread pool using boost::asio::thread_pool, why can't I reuse my threads?
C thread pool using boost::asio::thread_pool, why can't I reuse my threads?

Time:11-18

I am experimenting with boost::asio::thread_pool to create a thread pool in my application. I created the following toy example to see if I understand how it works but clearly not :)

#include <boost/asio/post.hpp>
#include <boost/asio/thread_pool.hpp>
#include <boost/bind.hpp>
#include <iostream>

boost::asio::thread_pool g_pool(10);

void f(int i) {
    std::cout << i << "\n";
}

int main() {
    for (size_t i = 0; i != 50;   i) {
        boost::asio::post(g_pool, boost::bind(f, 10 * i));
        g_pool.join();
    }
}

The program outputs

0

I am puzzled by two things: One, if I'm waiting for the threads to finish using g_pool.join(), why can I then not reuse the threads in the next iteration. I.e., I expected to also see the numbers 10,20,30,... printed in subsequent iterations etc.

Secondly, I'm creating a thread pool of size 10, why am I not at least seeing 10 outputs then? I cannot wrap my head around this.

Please let me know where I am going wrong, thanks in advance!

CodePudding user response:

You join the pool after posting the first task. So, the pool stops before you even accept a second task. That explains why you're not seeing more.

This fixes that:

for (size_t i = 0; i != 50;   i) {
    post(g_pool, boost::bind(f, 10 * i));
}
g_pool.join();

Addendum #1

In response to the comments. In case you want to wait for the outcome of a specific task, consider a future:

Live On Coliru

#include <boost/asio.hpp>
#include <boost/bind/bind.hpp>
#include <boost/thread.hpp>
#include <iostream>
#include <future>

boost::asio::thread_pool g_pool(10);

int f(int i) {
    std::cout << '('   std::to_string(i)   ')';
    return i * i;
}

int main() {
    std::cout << std::unitbuf;
    std::future<int> answer;

    for (size_t i = 0; i != 50;   i) {
        auto task = boost::bind(f, 10 * i);
        if (i == 42) {
            answer = post(g_pool, std::packaged_task<int()>(task));
        } else
        {
            post(g_pool, task);
        }
    }

    answer.wait(); // optionally make sure it is ready before blocking get()
    std::cout << "\n[Answer to #42: "   std::to_string(answer.get())   "]\n";

    // wait for remaining tasks
    g_pool.join();
}

With one possible output:

(0)(50)(30)(90)(110)(100)(120)(130)(140)(150)(160)(170)(180)(190)(40)(200)(210)(220)(240)(250)(70)(260)(20)(230)(10)(290)(80)(270)(300)(340)(350)(310)(360)(370)(380)(330)(400)(410)(430)(60)(420)(470)(440)(490)(480)(320)(460)(450)(390)
[Answer to #42: 176400]
(280)

Addendum #2: Serializing tasks

If you want to serialize specific tasks, you can use a strand. E.g. to serialize all the request based on the remainder of the parameter modulo 3:

Live On Coliru

#include <boost/asio.hpp>
#include <boost/bind/bind.hpp>
#include <boost/thread.hpp>
#include <iostream>
#include <future>

boost::asio::thread_pool g_pool(10);

int f(int i) {
    std::cout << '('   std::to_string(i)   ')';
    return i * i;
}

int main() {
    std::cout << std::unitbuf;
    
    std::array strands{make_strand(g_pool.get_executor()),
                       make_strand(g_pool.get_executor()),
                       make_strand(g_pool.get_executor())};

    for (size_t i = 0; i != 50;   i) {
        post(strands.at(i % 3), boost::bind(f, i));
    }

    g_pool.join();
}

With a possible output:

(0)(3)(6)(2)(9)(1)(5)(8)(11)(4)(7)(10)(13)(16)(19)(22)(25)(28)(31)(34)(37)(40)(43)(46)(49)(12)(15)(14)(18)(21)(24)(27)(30)(33)(36)(39)(42)(45)(48)(17)(20)(23)(26)(29)(32)(35)(38)(41)(44)(47)

Note that all work is done on any thread, but tasks on a strand happen in the order in which they were posted. So,

  • 0, 3, 6, 9, 12...
  • 1, 4, 7, 10, 13...
  • 2, 5, 8, 11, 14...

happen strictly serially, though

  • 4 and 7 don't need to happen on the same physical thread
  • 11 might happen before 4, because they're not on the same strand

Even More

In case you need more "barrier-like" synchronization, or what's known as fork-join semantics, see Boost asio thread_pool join does not wait for tasks to be finished (where I posted two answers, one after I discovered the fork-join executor example).

  • Related