Сокеты и многопоточность
От: b0r3d0m  
Дата: 04.08.16 06:35
Оценка:
Привет всем.

Появилась необходимость использовать асинхронные функции 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 конкретной версии) я запутался ещё больше.

Может ли кто-нибудь подсказать, как всё есть на самом деле?

Заранее благодарю.
 
Подождите ...
Wait...
Пока на собственное сообщение не было ответов, его можно удалить.