current situation
i implemented tcp server using boost.asio uses single io_service
object on call run
method single thread.
so far server able answer requests of clients immediately, since had necessary information in memory (no long-running operations in receive handler necessary).
problem
now requirements have changed , need information out of database (with odbc) - long-running blocking operation - in order create response clients.
i see several approaches, don't know 1 best (and there more approaches):
first approach
i keep long running operations in handlers, , call io_service.run()
multiple threads. guess use many threads have cpu cores available?
while approach easy implement, don't think best performance approach because of limited number of threads (which idling of time since database access more i/o-bound operation compute-bound operation).
second approach
in section 6 of this document says:
use threads long running tasks
a variant of single-threaded design, design still uses single io_service::run() thread implementing protocol logic. long running or blocking tasks passed background thread and, once completed, result posted io_service::run() thread.
this sounds promising, don't know how implement that. can provide code snippet / example approach?
third approach
boris schäling explains in section 7.5 of boost introduction how extend boost.asio custom services.
this looks lot of work. approach have benefits compared other approaches?
the approaches not explicitly mutually exclusive. see combination of first , second:
- one or more thread processing network i/o in 1
io_service
. - long running or blocking tasks posted different
io_service
.io_service
functions thread pool not interfere threads handling network i/o. alternatively, 1 spawn detached thread every time long running or blocking task needed; however, overhead of thread creation/destruction may noticeable impact.
this answer provides thread pool implementation. additionally, here basic example tries emphasize interaction between 2 io_services
.
#include <iostream> #include <boost/asio.hpp> #include <boost/bind.hpp> #include <boost/chrono.hpp> #include <boost/optional.hpp> #include <boost/thread.hpp> /// @brief background service function thread-pool /// long-standing blocking operations may occur without affecting /// network event loop. boost::asio::io_service background_service; /// @brief main io_service handle network operations. boost::asio::io_service io_service; boost::optional<boost::asio::io_service::work> work; /// @brief odbc blocking operation. /// /// @brief data data use query. /// @brief handler handler invoke upon completion of operation. template <typename handler> void query_odbc(unsigned int data, handler handler) { std::cout << "in background service, start querying odbc\n"; std::cout.flush(); // mimic busy work. boost::this_thread::sleep_for(boost::chrono::seconds(5)); std::cout << "in background service, posting odbc result main service\n"; std::cout.flush(); io_service.post(boost::bind(handler, data * 2)); } /// @brief functions continuation handle_read, /// invoked results odbc. void handle_read_odbc(unsigned int result) { std::stringstream stream; stream << "in main service, got " << result << " odbc.\n"; std::cout << stream.str(); std::cout.flush(); // allow io_service stop in example. work = boost::none; } /// @brief mocked read handler post work background /// service. void handle_read(const boost::system::error_code& error, std::size_t bytes_transferred) { std::cout << "in main service, need query odbc" << std::endl; typedef void (*handler_type)(unsigned int); background_service.post(boost::bind(&query_odbc<handler_type>, 21, // data &handle_read_odbc) // handler ); // keep io_service event loop running in example. work = boost::in_place(boost::ref(io_service)); } /// @brief loop show concurrency. void print_loop(unsigned int iteration) { if (!iteration) return; std::cout << " in main service, doing work.\n"; std::cout.flush(); boost::this_thread::sleep_for(boost::chrono::seconds(1)); io_service.post(boost::bind(&print_loop, --iteration)); } int main() { boost::optional<boost::asio::io_service::work> background_work( boost::in_place(boost::ref(background_service))); // dedicate 3 threads performing long-standing blocking operations. boost::thread_group background_threads; (std::size_t = 0; < 3; ++i) background_threads.create_thread( boost::bind(&boost::asio::io_service::run, &background_service)); // post mocked 'handle read' handler main io_service. io_service.post(boost::bind(&handle_read, make_error_code(boost::system::errc::success), 0)); // post mockup loop io_service show concurrency. io_service.post(boost::bind(&print_loop, 5)); // run main io_service. io_service.run(); // cleanup background. background_work = boost::none; background_threads.join_all(); }
and output:
in main service, need query odbc in main service, doing work. in background service, start querying odbc in main service, doing work. in main service, doing work. in main service, doing work. in main service, doing work. in background service, posting odbc result main service in main service, got 42 odbc.
note single thread processing main io_service
posts work background_service
, , continues process event loop while background_service
blocks. once background_service
gets result, posts handler main io_service
.
Comments
Post a Comment