Могут ли два потока разделить boost :: asio tcp socket для исключительно чтения и записи? [Дубликат]

Этот вопрос, скорее всего, является repost: Что {фигурные скобки} вокруг имени переменной javascript означают

Но в качестве ответа это назначение деструктуризации , Если ваш объект передается в зеркалах, на которые ссылается переменная, вы можете получить это конкретное поле во время назначения.

15
задан Community 23 May 2017 в 12:09
поделиться

9 ответов

Я провел обширные тесты и не смог сломать asio. Даже без блокировки каких-либо мьютексов.

Тем не менее я бы посоветовал вам использовать async_read и async_write с мьютексом вокруг каждого из этих вызовов.

Я считаю, что единственный обратный путь что ваши обработчики завершения могут быть вызваны одновременно, если у вас есть более одного потока, вызывающего io_service::run.

В моем случае это не было проблемой. Вот мой тестовый код:

#include <boost/thread.hpp>
#include <boost/date_time.hpp>
#include <boost/asio.hpp>
#include <vector>

using namespace std;
char databuffer[256];
vector<boost::asio::const_buffer> scatter_buffer;
boost::mutex my_test_mutex;
void my_test_func(boost::asio::ip::tcp::socket* socket, boost::asio::io_service *io) {
while(1) {
    boost::this_thread::sleep(boost::posix_time::microsec(rand()%1000));

    //my_test_mutex.lock(); // It would be safer 
    socket->async_send(scatter_buffer, boost::bind(&mycallback));
    //my_test_mutex.unlock(); // It would be safer
}
}
int main(int argc, char **argv) {

for(int i = 0; i < 256; ++i)
    databuffer[i] = i;

for(int i = 0; i < 4*90; ++i)
    scatter_buffer.push_back(boost::asio::buffer(databuffer));
boost::asio::io_service my_test_ioservice;
boost::asio::ip::tcp::socket my_test_socket(my_test_ioservice);
boost::asio::ip::tcp::resolver my_test_tcp_resolver(my_test_ioservice);
boost::asio::ip::tcp::resolver::query  my_test_tcp_query("192.168.1.10", "40000");
boost::asio::ip::tcp::resolver::iterator my_test_tcp_iterator = my_test_tcp_resolver.resolve(my_test_tcp_query);
boost::asio::connect(my_test_socket, my_test_tcp_iterator);
for (size_t i = 0; i < 8; ++i) {
    boost::shared_ptr<boost::thread> thread(
            new boost::thread(my_test_func, &my_test_socket, &my_test_ioservice));
}

while(1) {
    my_test_ioservice.run_one();
    boost::this_thread::sleep(boost::posix_time::microsec(rand()%1000));
}
return 0;

}

И вот мой временный сервер в python:

import socket
def main():
    mysocket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
    mysocket.bind((socket.gethostname(), 40000))
    mysocket.listen(1)

    while 1:
        (clientsocket, address) = mysocket.accept()
        print("Connection from: " + str(address))
        i = 0
        count = 0
        while i == ord(clientsocket.recv(1)):
            i += 1
            i %= 256

            count+=1
            if count % 1000 == 0:
                print(count/1000)
        print("Error!")
return 0

if __name__ == '__main__':
    main()

Обратите внимание, что запуск этого кода может заставьте ваш компьютер трэш.

4
ответ дан Climax 18 August 2018 в 19:31
поделиться
  • 1
    вы можете использовать typedef для сокращения некоторых из этого кода – gda2004 12 September 2012 в 09:48
  • 2
    Этот код на самом деле не проверяет его правильно. Он должен отправлять пакеты намного больше, чем 65536, чтобы проиллюстрировать, что asio не является по сути безопасным потоком для чередования вызовов async_write. – Climax 31 January 2013 в 16:42
  • 3
    Интересно! Поскольку я никогда не думал о отправке таких больших пакетов, похоже, что все будет в порядке. – ravenspoint 31 January 2013 в 17:10
  • 4
    Это может также произойти, когда внутренний буфер сокета заполнен (er) – Climax 8 September 2015 в 12:36
  • 5
    Кроме того, нити не делают ничего, чтобы решить эту проблему. – Climax 10 September 2015 в 09:25
  • 6
    Я всегда сомневался в том, что прядь, где это важно. – ravenspoint 10 September 2015 в 11:16
  • 7
    Мьютекс не защищает код, который асинхронно вызывается из критического раздела. – evoskuil 14 November 2016 в 09:27
  • 8
    Но я согласен с тем, что использование мьютекса вокруг других параллельных исполнений async_read и async_write - хорошая идея, потому что сокет не является потокобезопасным, и в противном случае он был бы одновременно доступен. – evoskuil 14 November 2016 в 09:35
  • 9
    Важное значение имеет наблюдение за чередованием. Также важно, чтобы люди узнали, что нить (неконкурентный вызов) не может ничего сделать для защиты от параллельного доступа к сокет асинхронными методами (async_read и async_write). Проект asio требует, чтобы чтения и записи вызывались в обработчиках чтения и записи. Это приводит к (1) неконкурентному вызову асинхронных методов, (2) неконкурентному выполнению запуска асинхронного кода и (3) избежанию чередования. К сожалению, это не говорит о потребностях истинного параллелизма. – evoskuil 14 November 2016 в 09:40
4
ответ дан Climax 7 September 2018 в 03:46
поделиться
4
ответ дан Climax 30 October 2018 в 07:55
поделиться

Это зависит от доступа к одному объекту сокета из нескольких потоков. Предположим, что у вас есть два потока, работающих с такой же функцией io_service::run().

Если, например, вы одновременно выполняете чтение и запись или можете выполнять операцию отмены из другого потока. Тогда это небезопасно.

Однако, если ваш протокол выполняет только одну операцию за раз.

  1. Если только один поток запускает прогон io_service, тогда проблем нет. Если вы хотите что-то выполнить в сокете из другого потока, вы можете вызвать io_service :: post () с обработчиком, который выполняет эту операцию в сокете, чтобы он выполнялся в том же потоке.
  2. Если у вас несколько потоки, выполняющие io_service::run, и вы пытаетесь сделать операции одновременно - скажем, отменить и прочитать операцию, тогда вы должны использовать пряди. В документации Boost.Asio есть учебное пособие.
0
ответ дан Artyom 18 August 2018 в 19:31
поделиться
  • 1
    Доступ к соке можно получить из нескольких потоков. Не все потоки используют io_service. – ravenspoint 9 September 2011 в 17:13

Похоже, этот вопрос сводится к следующему:

, что происходит, когда async_write_some() вызывается одновременно в одном сокете из двух разных потоков

Я считаю, что это точно операция, которая не является потокобезопасной. Порядок, в котором эти буферы выходят на провод, не определен, и они могут быть чередующимися. Особенно, если вы используете функцию удобства async_write(), так как она реализована как серия вызовов async_write_some() внизу, пока не будет отправлен весь буфер. В этом случае каждый фрагмент, который отправляется из двух потоков, может чередоваться случайным образом.

Единственный способ защитить вас от удара этого случая - это построить вашу программу, чтобы избежать подобных ситуаций.

Один из способов сделать это - написать буфер отправки прикладного уровня, который один поток отвечает за нажатие на сокет. Таким образом, вы можете защитить только буфер отправки. Имейте в виду, что простой std::vector не будет работать, так как добавление байтов в конец может привести к перераспределению его, возможно, пока есть выдающийся async_write_some(), ссылающийся на него. Вместо этого, вероятно, неплохо использовать связанный список буферов и использовать функцию разброса / сбора asio.

5
ответ дан Arvid 18 August 2018 в 19:31
поделиться

В соответствии с новатором 2008 года повышают 1,37 обновления asio, некоторые синхронные операции, в том числе записи «теперь потокобезопасны», позволяющие «одновременные синхронные операции в отдельном сокете, если они поддерживаются ОС» boost 1.37.0 history . Казалось бы, это поддерживает то, что вы видите, но упрощение предложения «Общие объекты: небезопасное» остается в файлах boost для ip :: tcp :: socket.

1
ответ дан Ben Bryant 18 August 2018 в 19:31
поделиться
  • 1
    Спасибо за проверку. Жаль, что никто не пришел, чтобы дать определенный ответ - просто другие читатели документов. Кстати, ваш редактор XML, лиса, камни! – ravenspoint 20 April 2012 в 18:16
2
ответ дан evoskuil 18 August 2018 в 19:31
поделиться

Используйте boost::asio::io_service::strand для асинхронных обработчиков, которые не являются потокобезопасными.

Строка определяется как строго последовательный вызов обработчиков событий (то есть без одновременных призывание). Использование нитей позволяет выполнять код в многопоточной программе без необходимости явной блокировки (например, с помощью мьютексов).

Учебник timer , вероятно, самый простой способ оберните голову вокруг нитей.

7
ответ дан Sam Miller 18 August 2018 в 19:31
поделиться
  • 1
    Прядь выглядит очень интересной и полезной звериной. Однако на первый взгляд это не кажется полезным в моем конкретном случае. Запись в сокет не контролируется одним и тем же io_service, но происходит в результате полностью независимого процесса. (+1 голос за полезность, в других случаях. – ravenspoint 9 September 2011 в 17:10
  • 2
    @ravenspoint один сокет обслуживается одной службой ввода-вывода, он принимает ссылку в конструкторе . Если вы используете несколько объектов io_service, это совершенно другая проблема. – Sam Miller 9 September 2011 в 17:19
  • 3
    Я не понимаю ваш предыдущий комментарий. Я отредактировал свой вопрос, чтобы уточнить, надеюсь, что мне нужно делать. – ravenspoint 9 September 2011 в 17:44
  • 4
    @ravenspoint вы смешиваете асинхронные методы с синхронными методами. Не делай этого. Строка полезна только в асинхронном контексте. – Sam Miller 9 September 2011 в 17:47
  • 5
    Это не выполняется, если методы, вызываемые нитями, не являются синхронными. Только вызов будет синхронным, что не будет препятствовать одновременному выполнению кода, инициированного асинхронным методом (методами). – evoskuil 14 November 2016 в 09:26

Ключом к пониманию ASIO является понимание того, что обработчики завершения выполняются только в контексте потока, который вызвал io_service.run(), независимо от того, какой поток называется асинхронным методом. Если вы только вызвали io_service.run() в одном потоке, то все обработчики завершения будут выполняться последовательно в контексте этого потока. Если вы вызвали io_service.run() в более чем одном потоке, то обработчики завершения будут выполняться в контексте одного из этих потоков. Вы можете думать об этом как о пуле потоков, где потоки в пуле - это те потоки, которые вызвали io_service.run() на одном и том же объекте io_service.

Если у вас есть несколько потоков, вызовите io_service.run(), тогда вы может заставить сериализовать обработчики завершения, помещая их в strand.

Чтобы ответить на последнюю часть вашего вопроса, вы должны позвонить boost::async_write(). Это отправит операцию записи в поток, который вызвал io_service.run(), и вызывается обработчик завершения, когда запись выполнена. Если вам нужно сериализовать эту операцию, это немного сложнее, и вы должны прочитать документацию по нитям здесь .

5
ответ дан Sean 18 August 2018 в 19:31
поделиться
  • 1
    Большая часть моей трудности состоит в том, что я не знаю, что есть и не является «обработчиком завершения». Is boost :: asio :: write () обработчик завершения? Будет ли «волшебным» очередь операции записи в (другой) поток, который вызвал метод run () для io_service, который был передан конструктору сокета? Если ответ на оба - да, тогда мой оригинальный код правильный, и я могу расслабиться! – ravenspoint 9 September 2011 в 18:58
  • 2
    @ravenspoint: boost :: asio :: write () - это синхронный вызов, поэтому он запускается, как только вы вызываете его в контексте потока, в котором происходит вызов. boost :: asio :: async_write () принимает объект функции как один из его аргументов. Когда вы вызываете boost :: asio :: async_write (), операция записи «магически» ставится в очередь на поток, который называется run (). – Sean 9 September 2011 в 19:12
  • 3
    @ravenspoint: Если вы работаете в linux, он работает примерно так: Когда вы вызываете io_service :: run (), базовый код вызывает epoll (), который блокирует текущий поток, пока не будет активность в одном из его файловых дескрипторов. Когда вы вызываете async_write (), он добавляет дескриптор файла сокета в набор epoll и связывает обработчик завершения с этим файловым дескриптором. Когда epoll возвращает базовый код, записывает данные и вызывает обработчик. Все это происходит в потоке, который называется run (). – Sean 9 September 2011 в 19:15
  • 4
    Я готов согласиться с тем, что async_write () работает так, как вы описываете. Уловка в том, что теперь я должен помешать написанию потока выполнить другой вызов async_write () до завершения первого. Вздох. Возможно, проще разместить функтора? По крайней мере, я уже написал и протестировал код. – ravenspoint 9 September 2011 в 19:24
  • 5
    @ravenspoint: Разница не в том, что один блок, а другой нет. Разница в том, что одна является синхронной, а другая - асинхронной. Чтобы выполнить асинхронную операцию, операция должна быть поставлена ​​в очередь. Вы можете выполнить синхронную операцию без очереди, что и делает ASIO. – Sean 9 September 2011 в 22:55
Другие вопросы по тегам:

Похожие вопросы: