Судя по статистике, предоставляемой blogger.com (да, я посматриваю на нее), некоторые сообщения этого блога читаются зарубежными пользователями - и даже (судя по ссылкам на гугло-переводчик) не русскоговорящими.
Уважаемые читатели, я с удовольствием отвечу на comments in English (feel free to write comments/questions in English). Единственная причина, по которой данный блог пока не имеет англоязычного двойника - это нехватка времени и сомнения в необходимости/интересе со стороны читателей.
среда, 15 июня 2011 г.
понедельник, 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, я понял:
Так вот: ответ (и сопутствующее обсуждение) на вопрос, заданный мной в данном сообщении, дополнительно должен помочь разработчикам, использующим 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 - еще не разбирался с ней).
зачем в требованиях 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, я понял:
- какая часть MyHandler::operator() относилась к invocation strategy;
- для чего вообще было введено понятие invocation strategy.
Так вот: ответ (и сопутствующее обсуждение) на вопрос, заданный мной в данном сообщении, дополнительно должен помочь разработчикам, использующим 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).
В дополнение:
P.S. Комментарии и даже "холивар" приветствуются.
Вспомнил, что в свое время я оставил один развернутый комментарий. "Развернутый" специально для дальнейшего использования в моих новых сообщениях/статьях о Boost.Asio.
Статей до сих пор нет, а тема "горячая"
Могу только пояснить следующее: как любая система построенная по шаблону 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. Комментарии и даже "холивар" приветствуются.
понедельник, 30 мая 2011 г.
Неудачное сочинение
Вот тут лежит неудачная статья для Хабра.
Неудачная, потом что:
1) какая-то "скомканная" получилась - особенно конец.
2) дальше песочницы не ушла.
Чтож, после доработки проекта echo_server возьмусь за нее с новыми силами. Писать документацию/описание для меня всегда было мучением.
Если кто-то (русскоговорящий) заинтересуется asio samples, то краткое пояснение он уже сможет почерпнуть по указанной выше ссылке.
Неудачная, потом что:
1) какая-то "скомканная" получилась - особенно конец.
2) дальше песочницы не ушла.
Чтож, после доработки проекта echo_server возьмусь за нее с новыми силами. Писать документацию/описание для меня всегда было мучением.
Если кто-то (русскоговорящий) заинтересуется asio samples, то краткое пояснение он уже сможет почерпнуть по указанной выше ссылке.
четверг, 19 мая 2011 г.
Презентации с BoostCon 2011
Начали появляться первые презентации с BoostCon 2011. Интересно, но мало. Ждем видео.
среда, 13 апреля 2011 г.
Пример работы с таймером (реализация таймаута)
Сегодня в проекты "echo_server" и "qt_echo_server" (т.к. у них общая кодовая база касаемо самого сервера) введена возможность указывать для сессий (на стороне сервера, конечно) таймаут чтения, по истечении которого сессия считается неактивной (по вине клиента - мол "упал он там что ли?").
При этом оставлена возможность не задавать величину подобного таймаута. В этом случае таймаут не будет использоваться вообще (это похоже на таймаут, равный +∞).
Отличительная возможность реализации таймаута - это использование custom memory allocation для обработчика, используемого совместно с deadline_timer-ом. Эта "фишка" потребовала особого дизайна (правда, не столь уж и "мудреного") и показала то, о чем не сказано ни в одном примере Boost.Asio. - см. комментарии - там есть ссылка на пост автора Asio, где очень подробно описано более простое (и понятное) решение. Кстати, это решение уже есть в офиц. документации Asio.
Код пока только в SVN-репозитории (trunk).
После проверки всеми компиляторами появится dev-release.
Updated 14.09.2011: см. улучшенный/исправленный вариант.
При этом оставлена возможность не задавать величину подобного таймаута. В этом случае таймаут не будет использоваться вообще (это похоже на таймаут, равный +∞).
Код пока только в SVN-репозитории (trunk).
После проверки всеми компиляторами появится dev-release.
Updated 14.09.2011: см. улучшенный/исправленный вариант.
пятница, 1 апреля 2011 г.
Qt Echo Server released
Всем, кому интересно, как связать многопоточный код с functor-based callbacks (например, Boost.Asio) и Qt signal/slot, смотреть проект qt_echo_server.
Решение простое и не использует никаких "хаков" - все по официальной документации Qt.
И так, Boost.Asio (+ Boost.Bind + Boost.Thread) + Qt (4.x) = qt_echo_server
Решение простое и не использует никаких "хаков" - все по официальной документации Qt.
И так, Boost.Asio (+ Boost.Bind + Boost.Thread) + Qt (4.x) = qt_echo_server
Подписаться на:
Сообщения (Atom)