вторник, 1 января 2030 г.

О блоге

Более двадцати лет я занимался разработкой ПО, в основном как программист и тим-лид, а в 2012-2014гг как руководитель департамента разработки и внедрения ПО в компании Интервэйл (подробнее на LinkedIn). Поэтому в моем блоге много заметок о работе, в частности о программировании и компьютерах, а так же об управлении.

Так же я пишу о жизни вообще и о нескольких своих увлечениях: о фотографии (включая публикацию своих фотографий, некоторые есть и на ZeissImages), о спорте, особенно о дартсе, и, совсем коротко, о кино.

понедельник, 31 декабря 2029 г.

[life.photo] Характерный портрет: вы и ваш мир моими глазами. Безвозмездно :)

Вы художник? Бармен или музыкант? Или, может быть, коллекционер? Плотник или столяр? Кузнец или слесарь? Владеете маленьким магазинчиком или управляете большим производством? Реставрируете старинные часы или просто починяете примус? Всю жизнь занимаетесь своим любимым делом и хотели бы иметь фото на память?

Предлагаю сделать портрет в обстановке, связанной с вашей работой или увлечением. Абсолютно бесплатно. Очень уж мне нравится фотографировать людей в их естественной среде. Происходить это может так...

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

[prog.c++14] Упрощение себе жизни за счет возможностей современного C++. На примере работы с сокетами

Уже неоднократно в блоге поднимал тему того, как использование возможностей C++ упрощает жизнь разработчику по сравнению с использованием теплой и ламповой "сишечки". Выдалось время показать еще один пример из недавней практики.

Временами при работе с сокетами нужно устанавливать те или иные опции. Если нужно просто дергать setsockopt, то в этом нет ничего сложного. Хотя, если нужно дернуть setsockopt много раз подряд для установки разных опций, то тиражирование простого кода методом копипасты наверняка приведет к тому, что где-то будут перепутаны значения level и optname.

Еще же веселее ситуация становится, когда кроме setsockopt приходится работать с msghdr, sendmsg и recvmsg. Тут мы быстро приходим к простому коду вида:

for (cm = CMSG_FIRSTHDR (&msg); cm; cm = CMSG_NXTHDR (&msg, cm)) {
   void *ptr = CMSG_DATA (cm);

   if (cm->cmsg_level == SOL_SOCKET && cm->cmsg_type == SO_TIMESTAMP) {
         struct timeval *tv = (struct timeval *) ptr;
         ... // bla-bla-bla
      }
   }
   else if (cm->cmsg_level == SOL_IP && cm->cmsg_type == IP_RECVERR) {
         struct sock_extended_err * ee = (struct sock_extended_err *) ptr;
         ... // bla-bla-bla
      }
   }
   else if (...)
      ...
}

Код-то простой. Но, как мне представляется, писать и сопровождать его готовы люди, которые не боятся сложностей. Я, например, боюсь :)

Тут ведь есть не только грабли с постоянными проверками cmsg_type и cmsg_level. Гораздо веселее -- это расчет размера буфера, который нужен для приема cmsghdr структур. Тут совсем несложно заблудиться в трех соснах. Например, взять опцию SOL_SOCKET/SO_TIMESTAMP. При вызове setsockopt для ее установки нужно задействовать int, который будет работать как bool. А при расчете размера cmsghdr нужно отводить место под timeval. При этом не забывая еще и про CMSG_LEN.

В общем, колупаться с такими подробностями в стиле plain old C -- это то еще удовольствие, как по мне. Поэтому...

пятница, 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-е. И только сейчас нашлось время дабы высказать свои соображения на счет того, имеет ли смысл ждать каких-то супер-пупер языков программирования, которые бы существенно упростили бы разработку больших программных проектов.