Ответ 1
Короче, последовательность. Службы пытаются удовлетворить ожидания пользователей, установленные службами Boost.Asio.
Использование внутреннего io_service
обеспечивает четкое разделение владения и контроля обработчиков. Если пользовательская служба помещает свои внутренние обработчики в пользователя io_service
, тогда выполнение внутренних обработчиков службы становится неявно связанным с обработчиками пользователей. Подумайте, как это повлияет на ожидания пользователей с помощью Boost.Asio Logger Service:
-
logger_service
записывает в поток файлов в обработчике. Таким образом, программа, которая никогда не обрабатывает цикл событийio_service
, такой как тот, который использует только синхронный API, никогда не будет записывать сообщения журнала. -
logger_service
больше не будет потокобезопасным, потенциально вызывающим поведение undefined, еслиio_service
обрабатывается несколькими потоками. - Время жизни внутренних операций
logger_service
ограничено значением времениio_service
. Например, когда вызывается функция serviceshutdown_service()
, время жизни владельцаio_service
уже завершено. Следовательно, сообщения не могли регистрироваться черезlogger_service::log()
внутриshutdown_service()
, поскольку он попытался бы отправить внутренний обработчик вio_service
, срок службы которого уже закончился. -
Пользователь может больше не принимать сопоставление "один к одному" между операцией и обработчиком. Например:
boost::asio::io_service io_service; debug_stream_socket socket(io_service); boost::asio::async_connect(socket, ..., &connect_handler); io_service.poll(); // Can no longer assume connect_handler has been invoked.
В этом случае
io_service.poll()
может вызывать обработчик, внутренний дляlogger_service
, а неconnect_handler()
.
Кроме того, эти внутренние потоки пытаются имитировать поведение, используемое внутренне с помощью Boost.Asio :
Реализация этой библиотеки для конкретной платформы может использовать один или несколько внутренних потоков для эмуляции асинхронности. Насколько это возможно, эти потоки должны быть невидимыми для пользователя библиотеки.
Пример монитора каталогов
В примере монитора каталогов используется внутренний поток для предотвращения бесконечной блокировки пользователя io_service
во время ожидания события. Как только событие произошло, обработчик завершения готов к вызову, поэтому внутренний поток отправляет обработчик пользователя пользователю io_service
для отложенного вызова. Эта реализация эмулирует асинхронность с внутренней нитью, которая в основном невидима для пользователя.
Подробнее, когда асинхронная работа монитора инициируется через dir_monitor::async_monitor()
, a basic_dir_monitor_service::monitor_operation
отправляется во внутренний io_service
. При вызове эта операция вызывает dir_monitor_impl::popfront_event()
, потенциально блокирующий вызов. Следовательно, если monitor_operation
отправляется пользователю io_service
, пользовательский поток может быть заблокирован на неопределенный срок. Рассмотрим влияние на следующий код:
boost::asio::io_service io_service;
boost::asio::dir_monitor dir_monitor(io_service);
dir_monitor.add_directory(dir_name);
// Post monitor_operation into io_service.
dir_monitor.async_monitor(...);
io_service.post(&user_handler);
io_service.run();
В приведенном выше коде, если io_service.run()
сначала вызывает monitor_operation
, тогда user_handler()
не будет вызываться до тех пор, пока dir_monitor
не увидит событие в каталоге dir_name
. Следовательно, реализация службы dir_monitor
не будет вести себя последовательно, что большинство пользователей ожидают от других служб.
Служба Asio Logger
Использование внутреннего потока и io_service
:
- Смягчает накладные расходы на ведение журнала в пользовательских потоках, выполняя потенциально блокирующие или дорогие вызовы во внутреннем потоке.
- Гарантирует безопасность потока
std::ofstream
, так как только один внутренний поток записывает в поток. Если ведение журнала было выполнено непосредственно вlogger_service::log()
или еслиlogger_service
отправил свои обработчики в пользовательскийio_service
, для обеспечения безопасности потоков потребуется явная синхронизация. Другие механизмы синхронизации могут приводить к большей нагрузке или сложности в реализации. -
Позволяет
services
регистрировать сообщения вshutdown_service()
. В уничтожениеio_service
будет:- Завершение работы каждого из своих сервисов.
- Уничтожьте всех неинвизированных обработчиков, которые были запланированы для отложенного вызова в
io_service
или любом из связанных с нимstrand
s. - Уничтожьте все свои службы.
По мере того, как срок службы пользователяio_service
закончился, его очередь событий не обрабатывается и не может быть отправлена дополнительная обработка обработчиков. Имея собственный внутреннийio_service
, который обрабатывается собственным потоком,logger_service
позволяет другим службам регистрировать сообщения во время ихshutdown_service()
.
Дополнительные соображения
При реализации пользовательской службы рассмотрим несколько моментов:
- Заблокировать все сигналы на внутренних потоках.
- Никогда не вызывайте код пользователя напрямую.
- Как отслеживать и отправлять обработчики пользователей при уничтожении реализации.
- Ресурсы (ы), принадлежащие службе, которые разделены между реализациями служб.
В последних двух точках объект dir_monitor
I/O демонстрирует поведение, которое пользователи не могут ожидать. Поскольку единственный поток внутри службы вызывает операцию блокировки в одной очереди событий реализации, он эффективно блокирует операции, которые потенциально могут быть немедленно завершены для их соответствующей реализации:
boost::asio::io_service io_service;
boost::asio::dir_monitor dir_monitor1(io_service);
dir_monitor1.add_directory(dir_name1);
dir_monitor1.async_monitor(&handler_A);
boost::asio::dir_monitor dir_monitor2(io_service);
dir_monitor2.add_directory(dir_name2);
dir_monitor2.async_monitor(&handler_B);
// ... Add file to dir_name2.
{
// Use scope to enforce lifetime.
boost::asio::dir_monitor dir_monitor3(io_service);
dir_monitor3.add_directory(dir_name3);
dir_monitor3.async_monitor(&handler_C);
}
io_service.run();
Хотя операции, связанные с handler_B()
(успех) и handler_C()
(прерванные), не будут блокироваться, единственный поток в basic_dir_monitor_service
заблокирован, ожидая изменения до dir_name1
.