В моем проекте я широко использую boost asio, чтобы иметь возможность равномерно ставить неоднородные события в модули моего приложения, используя io_service.post () и strand.post () / dispatch ().
В main () эти модули создаются и хранятся в shared_ptrs до тех пор, пока программа не выйдет и не будут удалены:
simplified main:
{
[some initialization]
boost::asio::io_service service;
[create pool of worker threads that drive the io_service]
{
boost::shared_ptr<manager_a> a(new manager_a(service, foo));
boost::shared_ptr<manager_b> b(new manager_b(service, bar, blah));
...
[wait for signal to shutdown (SIGINT (release), cin.getc() (debug), or similar)]
}
[some shutdown (join thread pool)]
}
«Менеджеры» (я не знаю, как их назвать) происходят от boost :: enable_shared_from_this.
В своих конструкторах они могут регистрировать обратные вызовы с другими модулями:
manager_a::manager_a(boost::asio::io_service& service, module *m) :
m_service(service),
m_strand(service),
m_module(m)
{
// manager_a implements some callback interface
// that "module" may call from another thread
m_module->register(this);
}
В деструкторе:
manager_a::~manager_a()
{
m_module->unregister(this);
}
Менеджер реализует интерфейс обратного вызова, отправив вызов через цепочку:
void manager_a::on_module_cb(module_message m)
{
// unsafe to do work here, because the callback is from an alien thread
m_strand.dispatch(boost::bind(&manager_a::handle_module_message, shared_from_this(), m));
}
void manager_a::handle_module_message(module_message m)
{
// safe to do work here, as we're serialized by the strand
}
Теперь дилемма, в которой я нахожусь:
Если «модуль» вызывает обратный вызов сразу после register(this)
в конструкторе, shared_ptr в main еще не перехватил экземпляр, а shared_from_this()
в функции обратного вызова выдает исключение. Та же проблема с деструктором - shared_ptr определил, что у него есть последняя ссылка, и когда деструктор вызывается, если обратный вызов вызывается до unregister(this)
, shared_from_this()
бросков.
Причина, по которой требуется shared_from_this()
, заключается в том, что вызовы функций из очереди хранятся в io_service, который содержит указатель на экземпляр менеджера и который не зависит от времени жизни менеджера. Я могу дать необработанный this
«модулю», потому что я могу отменить его регистрацию до уничтожения manager_a, но это не относится к вызовам функций из очереди в io_service, поэтому shared_from_this()
необходим для поддержания экземпляра живым до тех пор, пока есть какое-то сообщение. Вы также не можете отменить их, и вы не можете ждать блокировки в деструкторе, пока все ожидающие сообщения не будут доставлены. К сожалению, как и сейчас, я даже не могу предотвратить (постарались) поставить новые посты в очередь на критических фазах конструктора / деструктора.
Некоторые идеи:
Напишите функции запуска / остановки и зарегистрируйте / отмените регистрацию там. Например. создайте заводскую функцию следующим образом:
boost::shared_ptr<manager_a> manager_a::create(io_service& s, module *m)
{
boost::shared_ptr<manager_a> p(new manager_a(s, m));
p->start();
return p;
}
Это работает только для создания, а не для разрушения. Предоставление пользовательского средства удаления для shared_ptr, который вызывает stop () перед удалением, не помогает, так как это слишком поздно.
Есть вызовы main () start () и stop (), но я не понимаю, как main () может сделать это безопасным для исключений способом для вызова stop (), т.е. обязательно Вызовите stop (), даже если какой-нибудь более поздний код бросит Еще один класс RAII для вызова stop () кажется неудобным.
Просто оберните вызов strand.dispatch () в try / catch и игнорируйте исключение. Это признак того, что уничтожение выполняется (или строительство не завершено), поэтому просто игнорируйте обратный вызов. Это кажется хакерским, и я бы предпочел этого не делать. Если бы у меня был доступ к внедренному слабому_приекту в базовом классе enable_shared_from_this
, я мог бы вызвать ненулевой lock()
и проверить возвращаемое значение, чтобы убедиться, что оно допустимо. Но shared_from_this не дает вам доступа, и все равно будет казаться хакерским ... Кроме того, пропуск обратного вызова во время построения не поможет, даже если бы я мог обойти это.
Пусть manager_a имеет свой собственный io_service и поток, управляющий им. Затем в деструкторе я могу остановить службу и присоединиться к потоку и убедиться, что в io_service нет ожидающих сообщений. Также нет необходимости в shared_from_this()
или в пряди. Я думаю, это сработает, но тогда пул потоков в main () для всех менеджеров станет бессмысленным, и в моем приложении будет гораздо больше потоков, чем кажется разумным. В других модулях уже достаточно потоков, которые не используют asio io_service ...