For a new project at my new job, the tech lead and architect asked me to learn and use boost::asio. Specifically the class thread_pool of the library.
Let me put things in context. We are building an application that will make complex calculations. The user sends an XML as input with all the data that we need inside and the project I'm developping, should take that XML, retrieve all the infos it needs and then, launch and execute a bunch of calculations. Finally, the result must be sent back to the user. This is all done asynchronously.
Now, the architect wants the project to be a micro service taking requests from the users. And in order to make as many calculations as possible, we should have a thread pool of calculators.
Each thread would dig into the queue of requests by users. Make the calculcation, return the result and if the queue is not empty, then take the next request. To accomplish this, Boost Asio was chosen. So, my question is really specific to Boost Asio. We don't intend to change the tool. Boost asio contains a class thread_pool that seems to be designed to solve this kind of problems.
I'm still at the beginning phase and I tried to see how it works. Unfortunately, even this simple code that I wrote today, seems not working all the time. Sometimes it works, sometimes it hang and nothing happens....which for me is a sign of deadlock. I'm not sure.
Could you tell me please what's the issue with it ? Cheers,
#include <boost/asio/post.hpp>
#include <boost/asio/thread_pool.hpp>
#include <boost/asio/use_future.hpp>
#include <cstdlib>
#include <exception>
#include <future>
#include <iostream>
#include <mutex>
#include <thread>
int main()
{
boost::asio::thread_pool pool{5};
std::mutex mutex;
for(int i = 0; i < 5; ++i)
{
boost::asio::post(pool, [i, &mutex]() {
try
{
std::lock_guard<std::mutex> lg{mutex};
std::cout << "Task " << i << " executed by thread " << std::this_thread::get_id()
<< std::endl;
}
catch(const std::exception& e)
{
std::cerr << e.what() << '\n';
}
});
}
std::future<int> r1 = boost::asio::post(pool, boost::asio::use_future([]() { return 2; }));
{
std::lock_guard<std::mutex> lg{mutex};
std::cout << "Result = " << r1.get() << '\n';
}
// Optional: Wait for all tasks to complete
pool.join();
return EXIT_SUCCESS;
}
I ran the code and sometimes everything is ok but other times, it just hang and nothing happens.
Yes. The deadlock potential exists because you do a blocking wait on the future in main
: r1.get()
while holding the console output mutex. If you wait OUTSIDE the lock, the deadlock potential is gone: Live On Coliru:
r1.wait();
{
std::lock_guard<std::mutex> lg{console_mx};
std::cout << "Result = " << r1.get() << '\n';
}
Note, using std::osyncstream
instead avoids the pitfall altogether: Live On Coliru
#include <boost/asio.hpp>
#include <iostream>
#include <syncstream>
namespace asio = boost::asio;
static thread_local unsigned const thread_id = [] {
static std::atomic<unsigned> id = 0;
return id++;
}();
static auto out() { return std::osyncstream{std::cout}; }
int main() {
asio::thread_pool pool{5};
for (int i = 0; i < 5; ++i)
post(pool, [i] { out() << "Task " << i << " executed by thread " << thread_id << std::endl; });
std::future<int> r1 = post(pool, asio::use_future([]() { return 2; }));
// r1.wait();
out() << "Result = " << r1.get() << '\n';
pool.join();
}