Если читать советы по boost то пропагандируется идеология:
1 io_service и N потоков к нему на все классы в программе. Чтобы просто увеличив количество потоков улучшить производительность программы.
Звучит хорошо и красиво, примеры на boost.org вовсю доступно и понятно описывают реализацию для TCP-сервера и TCP-соединения. Но не более того!! Всё что выше по иерархии (т.е. просто архитектуру такой программы) почему-то "забыли упомянуть", или видимо посчитали что "дальше очевидно", хотя на мой взгляд там начинается самое интересное!!!.
Итак приведу странный пример, который меня очень беспокоит (исходники если надо выложу потом):.
1. Класс TcpConnection (унаследован от enable_shared_from_this<TcpConnection>). Получает movable boost::asio::ip::tcp::socket.
Имеет 2 сигнала
// сигнал прочитанных данных
boost::signals2::signal<void(shpTcpConnectionS, const DataPortion&)> m_OnDataReaded;
2. Класс ProtocolParser — этакий парсер некоего юзерского протокола. Принимает io_service& в конструкторе и shared_ptr<TcpConnection>. Предполагается что получая данные от shared_ptr<TcpConnection> выполняется парсинг каких-то данных, возможно отправка каких-то данных в соединение. По факту приёма каких-то особых данных может исторгнуть из себя посредством boost::signals2::signal запросы на какие-то действия.
3. TCP-сервер аля из примеров на boost.org (получает в конструкторе io_service&). Исторгает из себя новое соединение через boost::signals2::signal<void(std::shared_ptr<TcpConnection>)> m_OnNewConnection;
4. Класс Controller, который по сути "владелец всего".
Содержание:
1. Один io_service
2. N потоков на с запущенным io_service::run() для io_service из п.1
3. TCP-сервер
4. map<id, shared_ptr<ProtocolParser> >
5. strand
Связи и поведение всей программы:
Сигнал TCP-сервера m_OnNewConnection завязан на обработчик в Controller. Диспетчеризация через strand. На каждое новое соединение создаётся свой shared_ptr<ProtocolParser>, ему передаётся в работу shared_ptr<TcpConnection>. Новый shared_ptr<ProtocolParser> укладывается в map.
Сигналы от каждого ProtocolParser подключены на обработчики Controller. Т.е. фактически выполняется управление контроллером через TCP-соединение.
Примерно так.
Всё это более менее прекрасно работает до тех пор пока вдруг Controller'у вдруг не приспичит "убить" какой-то ProtocolParser (причём ProtocolParser должен находиться в состоянии активного обмена по TCP).
Совершенно нет проблем это сделать если каждый объект класса (Controller, ProtocolParser, TcpConnection, TcpServer) владеет своей парой io_service+поток.
Интересно как это сделать при методологии 1 io_service и N потоков на всю программу?
Ведь при попытке удалить ProtocolParser нужно отменить все операции на связанном с ним shared_ptr<TcpConnection> и удалить на него ссылку. Но это не решает проблему, т.к. один из потоков в данный момент обслуживающий TcpConnection может уже передавать данные в ProtocolParser, который находится в состоянии уничтожения/уничтожился.
Один из вариантов что я вижу — цеплять к сигналу TcpConnection::m_OnDataReaded обработчик не голый указатель на ProtocolParser а также shared_ptr<ProtocolParser> (и ProtocolParser унаследован от enable_shared_from_this). Но это всё выглядит очень печально, т.к. данный пример можно развивать дальше. ProtocolParser может быть связан с неким классом A (со своей логикой), который в свою очередь связан с классом B,... Городить на каждый signal-connect shared_ptr выглядит очень странно.