Привет всем.
Появилась необходимость использовать асинхронные функции boost::asio для написания клиент-серверного приложения, отправляющего и принимающего некоторые данные по сети.
Меня интересует следующее -- какие ограничения накладывает boost::asio на одновременное использование одного и того же объекта ip::tcp::socket из нескольких потоков?
Первым делом я, разумеется, направился в
документацию, в которой, казалось бы, чётко сказано следующее:
Thread Safety
Distinct objects: Safe.
Shared objects: Unsafe.
Раз это написано после перечисления всех имеющихся в интерфейсе socket'а функций, я делаю логичный вывод о том, что даже одновременный вызов async_read и async_write (несмотря на фулл-дуплексность TCP) ведёт к UB:
// thread 1
sock.async_read(...); // вызывается одновременно с async_write из потока 2
// thread 2
sock.async_write(...); // вызывается одновременно с async_read из потока 1
// thread 3 (единственный поток, вызывающий run у соответствующего объекта io_service)
while (true)
{
io_service.run();
}
Смотрел примеры в документации boost'а (например,
этот) и заметил то, что они оборачивают в strand только
handler'ы, а не сами вызовы функций async_write и async_read. С другой стороны, конкретно в приведённом мной примере (и, собственно, это единственный пример, содержащий одновременно сокеты и strand) у них логически не может произойти ситуации с одновременным вызовом функций одного и того же сокета. Всё начинается со start'а и лишь по результатам его деятельности вызывается следующая функция:
void connection::start()
{
socket_.async_read_some(boost::asio::buffer(buffer_),
strand_.wrap(
boost::bind(&connection::handle_read, shared_from_this(),
boost::asio::placeholders::error,
boost::asio::placeholders::bytes_transferred)));
}
void connection::handle_read(const boost::system::error_code& e,
std::size_t bytes_transferred)
{
// ...
socket_.async_read_some(boost::asio::buffer(buffer_),
strand_.wrap(
boost::bind(&connection::handle_read, shared_from_this(),
boost::asio::placeholders::error,
boost::asio::placeholders::bytes_transferred)));
// ...
}
Да и вообще, даже если мы завернём
handler'ы таких функций, как async_read и async_write, означает ли это то, что сами операции I/O (в данном случае отправки данных на сокет и получения их из буфера) не будут выполнены одновременно? Ведь в доках говорится именно про handler'ы, а не сами операции чтения-записи:
A strand is defined as a strictly sequential invocation of event handlers (i.e. no concurrent invocation)
Может быть, мне логически без разницы, могут ли несколько handler'ов вызваться одновременно друг с другом -- разделяемых ресурсов там может и не быть или быть, но под защитой какого-то собственного примитива синхронизации (например, mutex).
Вопрос гуглится, но после прочтения кучи ответов на SO (которые либо не подтверждены словами из документации, либо основаны на выяснении деталей реализации boost::asio конкретной версии) я запутался ещё больше.
Может ли кто-нибудь подсказать, как всё есть на самом деле?
Заранее благодарю.