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:
#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:
#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).