пятница, 22 июля 2016 г.

[prog.c++] Удивлен поведением asio::io_service::run и asio::io_service::stopped

Попробовал сделать вспомогательный класс, который бы содержал внутри себя экземпляр asio::io_service и пул рабочих нитей, на которых бы дергались методы asio::io_service::run. Как водится, документацию внимательно прочитал только после того, как обнаружил, что ничего не работает. Ну а когда прочитал, был несколько удивлен.

Оказывается io_service::run возвращает управление сразу же, если сейчас в io_service нет никаких задач. Т.е., если нет ни таймеров, ни отслеживаемых событий ввода-вывода, то io_service::run сразу же завершает свою работу и возвращает управление.

С одной стороны, такое поведение можно понять. Если приложение полностью базируется на asio, то в нем всегда будет что-то в очереди задач io_service. А когда не будет, то значит приложение завершило все свои действия и можно прекращать работу.

Но вот в моем случае оказалось не так. У меня необходимость выполнения операций ввода-вывода возникает время от времени. Когда возникает, я выдаю задачи io_service, когда не возникает, io_service ничего не делает.

Первоначальная задумка была в том, чтобы создать пул рабочих потоков для asio. Каждый поток дернул бы io_service::run и заснул бы, если у asio нет задач. Задачи появились -- потоки проснулись, отработали свое и заснули бы вновь. Перед завершением работы я бы дернул io_service::stop, рабочие потоки бы проснулись, на них бы завершились вызовы io_service::run и все остались бы довольны.

Но нет, так просто не получилось. Когда задач для asio нет, рабочие потоки сразу же вылетают из io_service::run и сразу же завершают свою работу. Чтобы они не завершали свою работу сразу же, нужно либо делать в рабочих потоках цикл, в котором постоянно дергается io_service::run, либо придумывать что-то еще.

В итоге сделал схему, в которой создается один лишний таймер, срабатывающий раз в час. Срабатывающий только для того, чтобы перезадать себя самого еще на час. А потом еще на час и т.д.

Наличие этого таймера создает для io_service видимость наличия работы, которая еще не выполнена. Что и не позволяет вызовам io_service::run на рабочих потоках завершаться сразу же. Завершение происходит только тогда, когда в конце работы всего приложения явным образом вызывается io_service::stop.

В общем, был удивлен поведению io_service::run. Такое впечатление, что ее задизайнили под такие сценарии использования, когда у приложения нет никаких других задач, кроме ввода-вывода. Еще больше я удивился тому, что io_service::stopped возвращает true не только в том случае, когда io_service::stop вызывали явно. Но и в том случае, когда io_service::stop вообще не вызывали, а у io_service просто не оказалось никакой работы.

Остается надеяться, что это просто я из-за недостатка опыта работы с asio что-то не так понял, и что пул рабочих потоков для io_service::run можно организовать более прямым образом, без обходных маневров с липовыми таймерами.

четверг, 21 июля 2016 г.

[prog.c++14] Возможности современного C++ оказываются очень в тему при работе с Asio

Как только берешься за Asio, так сразу фишки из С++11/14 становятся ну очень полезными:

class handler_t : public std::enable_shared_from_this< handler_t >
{
   ...
   templatetypename H, typename... T >
   auto wrap_handler( H handler, T && ...tail )
   {
      return m_strand.wrap( std::bind( handler, shared_from_this(), std::forward<T>(tail)... ) );
   }
   ...
   void
   activate( const endpoint & receiver_endpoint )
   {
      m_timer.expires_from_now( std::chrono::seconds(2) );
      m_timer.async_wait( wrap_handler( &handler_t::on_timer, _1 ) );

      m_socket.async_connect( receiver_endpoint,
            wrap_handler( &handler_t::on_connect, _1 ) );
   }
   ...
}

Не просто было бы в рамках С++03 написать вот такой простенький wrap_handler.

PS. В тему будет и вот эта заметка двухлетней давности. Отрадно отметить, что высказанные тогда надежды на бодрое развитие C++14 и C++17 пока оказались не напрасными.

[prog.thoughts] ...такое впечатление, что асинхронность на коллбэках может быть сильно разной...

Рассказывая на публике об акторах вообще и о SObjectizer в частности, довольно часто сталкиваешься со мнением, что асинхронность на коллбэках -- это не есть хорошо. Мол, код быстро превращается в лапшу, разобраться в которой бывает непросто. Это меня всегда несколько удивляло, т.к. события у SObjectizer-овских агентов -- это те самые асинхронные коллбэки, но особых проблем с превращением агентов в лапшеподобный код не замечалось.

А вот сейчас плотно столкнулся с Asio и, похоже, стал понимать, о чем именно речь. Когда дергаешь async_connect и вешаешь на него один коллбэк, затем из этого коллбэка дергаешь async_write со вторым коллбэком, из которого дергается async_read с третьим коллбэк-ом... И все эти взаимосвязи можно проследить только в местах вызова async_* методов... Да, становится понятно о какой лапше из коллбэков говорят люди.

Видимо, мы в SObjectizer-е с такими вещами особо не сталкиваемся, т.к. операция подписки агента, как правило, выполняется всего в одном месте (либо в конструкторе агента, либо в методе so_define_agent). Из-за чего разобраться во взаимосвязях событий и сообщений оказываться проще, чем в случае с разбросанным по всему коду вызовами async_*. Да и привязка событий к состояниям так же улучшает восприятие логики работы агента.

Вроде мелочь, а приятно. Впрочем, поработаю с Asio еще, посмотрю, не поменяется ли мнение :)

вторник, 19 июля 2016 г.

[prog.thoughts] Могут ли языки программирования существенно упростить разработку больших проектов?

Сначала состоялся вот этот разговор. Потом на глаза попалась вот эта тема на LOR-е. И только сейчас нашлось время дабы высказать свои соображения на счет того, имеет ли смысл ждать каких-то супер-пупер языков программирования, которые бы существенно упростили бы разработку больших программных проектов.