пятница, 24 июня 2011 г.

Utilizing ConnectEx with asio::windows::overlapped_ptr

Наверное многие Windows-программисты чесали репу смущались, когда узнавали, что Asio реализует асинхронный socket::async_connect при помощи select и дополнительного скрытого/внутреннего потока. Видимо, все дело в поддержке Windows 2000 - функция ConnectEx (судя по MSDN) появляется только в Windows XP.

Почему-то сегодня утром я вспомнил про интересный пример с использованием произвольной IOCP-related WinAPI функции совместно с Asio. Вспомнил и про многострадальный ConnectEx (до сегодняшнего утра ConnectEx использовать не приходилось - занимался только серверами и никогда не занимался клиентами).

Так вот - скрестить asio::windows::overlapped_ptr и ConnectEx оказалось очень просто - 2 часа и первый вариант готов.

Всех интересующихся прошу в SVN на sf.net. Самая интересная часть кода - очередной copy/paste из Asio examples (transmit_file.cpp).

Viva Asio!

Updated: проект async_connect уже включен в новый downloadable пакет.

среда, 15 июня 2011 г.

English detected

Судя по статистике, предоставляемой blogger.com (да, я посматриваю на нее), некоторые сообщения этого блога читаются зарубежными пользователями - и даже (судя по ссылкам на гугло-переводчик) не русскоговорящими.
Уважаемые читатели, я с удовольствием отвечу на comments in English (feel free to write comments/questions in English). Единственная причина, по которой данный блог пока не имеет англоязычного двойника - это нехватка времени и сомнения в необходимости/интересе со стороны читателей.

понедельник, 13 июня 2011 г.

Design Journeys with Asio

Предлагаю читателям этого блога ответить на довольно простой вопрос:

зачем в требованиях Asio к функтору-обработчику существует asio_handler_invoke?

Ответы/догадки оформлять в виде комментариев. Желательно написать что-то свое, а не просто привести ссылку на документацию Asio - в ней нет краткого обобщенного ответа.

Updated:
Я неправильно выразил вопрос. Кхм... скажем по-проще: как Вы думаете, зачем автор Asio усложнил дизайн, введя asio_handler_invoke (будем называть это invocation strategy)? Что потребовало от него именно такого похода?

Вопрос отчасти связан с тем, что многие считают Asio слишком сложной библиотекой. Этот вопрос направлен на то, чтобы объяснить, что в Asio нет ничего "случайного". Все имеет свою вескую причину.

Жаль, что на BoostCon 2011 Chris не сделал доклада с заголовком, похожим на заголовок данного сообщения.

Updated:
Когда я только начинал использовать Boost.Asio, наличие invocation strategy меня несколько смутило: что именно в моем MyHandler::operator() относится к invocation strategy?

И вот, когда я снова перечитал документацию Boost.Asio и нашел пункт X, я понял:
  1. какая часть MyHandler::operator() относилась к invocation strategy;
  2. для чего вообще было введено понятие invocation strategy.
Позже я понял, что invocation strategy лучше вынести из MyHandler::operator(), т.е. оставить ее только в соотв. asio_handler_invoke. В противном случае надо быть готовым к рекурсивному использованию invocation strategy. Например, вместо boost::mutex использовать boost::recursive_mutex (см. MA_BOOST_ASIO_HEAVY_STRAND_WRAPPED_HANDLER в asio-samples).

Так вот: ответ (и сопутствующее обсуждение) на вопрос, заданный мной в данном сообщении, дополнительно должен помочь разработчикам, использующим Boost.Asio, понять - какую часть MyHandler::operator() следует выносить в asio_handler_invoke и как выносить данный код - оставляя его же в MyHandler::operator() или нет?

Updated:
Как показало время, этот вопрос оказался либо неинтересным, либо слишком сложным.

Ну что ж, вот правильный ответ: Strands: Use Threads Without Explicit Locking.

Читаем: "if a completion handler goes through a strand, then all intermediate handlers should also go through the same strand. This is needed to ensure thread safe access for any objects that are shared between the caller and the composed operation (in the case of async_read() it's the socket, which the caller can close() to cancel the operation). This is done by having hook functions for all intermediate handlers which forward the calls to the customisable hook associated with the final handler".

Объясняю проще: единственная причина введения asio_handler_invoke - это правильная работа composed operations в свете синхронизации доступа к IoObject. Только такой hook и мог помочь в данном случае. Все остальные места в нем особо не нуждались, так как они так или иначе всегда заканчиваются вызовом Handler::operator() без необходимости повторного доступа к IoObject (могу ошибаться насчет поддержки SSL - еще не разбирался с ней).

пятница, 10 июня 2011 г.

Не пропадать же зря...

На днях просматривал forum.vingrad.ru.

Вспомнил, что в свое время я оставил один развернутый комментарий. "Развернутый" специально для дальнейшего использования в моих новых сообщениях/статьях о Boost.Asio.

Статей до сих пор нет, а тема "горячая" (ёпрст, многие, кто использует Asio, до сих пор не знают этого. Я бы вообще не стал использовать Asio, не будь в нем такой возможности, так как в этом случае raw-C-written-WinSock-based сервер давал бы ощутимое превосходство в скорости) - так что приведу выдержку из того комментария здесь:

Могу только пояснить следующее: как любая система построенная по шаблону Proactor (да и вообще - как все многопоточные системы, базирующиеся на очередях - т.е. почти все более-менее "продвинутые" многопоточные системы, например, Intel TBB), asio активно использует очереди (в случае asio - это очереди из функциональных объектов).

В связи с этим остро встает вопрос выделения памяти в таких очередях (особенно остро в свете многопоточности и кешей процессора). Intel TBB решает это своим "прокаченным-специализированным" аллокатором (и обещаниями повторно использовать выделяемые блоки).

asio использует очень легкое и гибкое решение (и это не единственный случай!): позволяет использовать allocator per async operation (начиная с dev release 1.53 - во всех асинхронных операциях). И вот какая штука: в любом вменяемом (т.е. не "hello world") приложении количество одновременных однотипных асинхронных операций над одним и тем же объектом (например, socket или deadline_timer) всегда ограничено сверху. И зачастую это ограничение равно единице (Вы уже на него нарвались недавно).

Исходя из всего вышесказанного, зачастую, Вы можете просто заранее выделить непрерывный блок памяти, который будет удерживаться Вашим handler-ом (например, при помощи shared_ptr). Затем, каждый раз когда asio будет нужна память для чего-то временного (обычно, это память для расположения в очереди - IOCP или boost::asio::io_service::strand или даже deadline_timer), что связанно с асинхронной операцией, для которой Ваш handler является completion handler-ом - asio просто попросит об этом через Ваш же handler. При этом те гарантии что дает asio относительно custom memory allocation, гарантируют, что (при достаточном размере Вашего пред-аллоцированного блока памяти) Вы всегда сможете выделить эту память из того самого, ранее выделенного, блока памяти.

Кроме того, гарантии asio позволяют удерживать этот "пред-аллоцированный блок памяти" самим же handler-ом (с небольшим ограничением на handler - см. доки по Boost.Asio 1.53, почти всегда это ограничение выполняется и уж точно всегда его можно легко обойти). Главное, хранить в handler-е не сам блок, а указатель на него (лучше -smart) - чтобы в блок помещался сам handler + что-то временное.

В моем проекте (http://sourceforge.net/projects/asio-samples) echo_server (и qt_echo_server, и, вообще, все "asio samples") вообще не выделяет память в куче при своей непосредственной работе (кроме приема новых входящих TCP-соединений, да и тут есть reuse). Custom memory allocation сводит все выделения памяти к тому коду, что я указал - т.е. никаких блокировок и даже никаких атомарных (interlocked) операций. Плюс, в теории (не могу до сих пор проверить) - это должно очень положительно сказаться на использовании CPU-кеша (Updated: и на branch prediction) (+ учтите очень правильную реализацию strand-ов в asio).

В дополнение:
  • см. ma/context_alloc_handler.hpp, ma/custom_alloc_handler.hpp и ma/handler_allocator.hpp (это практически copy-paste из примеров Chris-а и из private/details-"внутренностей" Boost.Asio, так что все лавры направляйте в сторону автора Asio).
  • см. документацию по Asio Custom Memory Allocation.

P.S. Комментарии и даже "холивар" приветствуются.