Странное поведение io_context или std::cout на многопоточном UDP-сервере

У меня есть простой UDP-сервер, созданный с использованием boost\asio, который на данный момент печатает только полученные данные, как показано ниже.

using boost::asio::ip::udp;

enum { max_length = 2048 };

void server(boost::asio::io_context& io_context, unsigned short port)
{
    std::cout << "Server Created on port " + std::to_string(port) << " \n" << std::flush;
    udp::socket sock(io_context, udp::endpoint(udp::v4(), port));
    while(true)
    {
        udp::endpoint sender_endpoint;
        std::vector<char> v(max_length);
        sock.receive_from(boost::asio::buffer(v), sender_endpoint);
        for (int i = 0; i < v.size(); i++)
        {
            std::cout << v[i] <<std::flush;
        }
        std::cout << std::endl;
    }
}

Я создаю 3 потока функции server, используя boost::thread, которые я назначаю thread_group:

boost::asio::io_context io_context;

            boost::thread_group tg;

            boost::thread *t = new boost::thread(server, std::ref(io_context), 8889);
            boost::thread *t1 = new boost::thread(server, std::ref(io_context), 8890);
            boost::thread *t2 = new boost::thread(server, std::ref(io_context), 11111);

            tg.add_thread(t);
            tg.add_thread(t1);
            tg.add_thread(t2);

            tg.join_all();

Для тестирования сервера я использовал Packet Sender. Проблема в том, что вывод... зашифрован. При отправке пакетов на 3 порта в одно и то же время (более или менее) один раз в секунду вывод немного смещается, но при увеличении частоты пакетов до одного раза в 0,1 секунды вывод становится нечитаемым, как показано на эти два изображения. Я пытался дать один отдельный объект io_context каждому серверу, но вывод остается одинаковым при высокой частоте. Есть ли способ избежать этого?


person WKstraw    schedule 25.02.2020    source источник
comment
Я не очень разбираюсь в бусте, так что, может быть, я говорю дураку. Это помогает? stackoverflow.com/questions/6374264/ I кажется, что решение должно состоять в том, чтобы просто использовать общий мьютекс cout, который вы передаете каждому потоку.   -  person JohnFilleau    schedule 25.02.2020
comment
io_context не имеет отношения к std::cout. Вам нужно использовать какой-то метод синхронизации, чтобы избежать скремблирования. Например, вы можете использовать std::mutex и заблокировать его при выводе в std::cout.   -  person sklott    schedule 25.02.2020
comment
@Mgetz Читайте внимательно (individual characters output from multiple threads may interleave, but no data races occur)   -  person sklott    schedule 25.02.2020


Ответы (2)


std::cout автоматически делает некоторую блокировку для вас, одна операция печати не будет перекрываться другой печатью из другого потока. Однако, поскольку вы печатаете по одному символу за раз, вывод каждого потока, вероятно, будет перекрываться. Сброс после каждого напечатанного символа также может привести к снижению производительности.

Если вы построите то, что хотите напечатать, в одну строку, она должна быть напечатана правильно:

    std::vector<char> v(max_length);
    size_t bytes = sock.receive_from(boost::asio::buffer(v), sender_endpoint);
    std::string str(v.data(), bytes);
    str += '\n';
    std::cout << str;

Или вы можете пропустить вектор и сохранить прямо в строку:

    std:: string str(max_length);
    size_t bytes = sock.receive_from(boost::asio::buffer(str), sender_endpoint);
    str.resize(bytes)
    str += '\n';
    std::cout << str;
person Alan Birtles    schedule 25.02.2020
comment
Вау, удивительно, как я даже не подумал об этом. Благодарю вас! - person WKstraw; 25.02.2020

Логичное (и правильное) решение — использовать взаимное исключение на std::cout. Вы знаете соответствующий объем блокировки (в вашем случае это всего лишь один UDP-пакет, но std::cout не может об этом догадаться).

Причудливое решение — boost.asio.strand. Вам это не нужно для таких простых случаев, как этот, но поскольку вы пытаетесь использовать boost.asio.io_context, вы должны знать, что в boost.asio есть еще один класс, который может работать так, как вы предполагали.

person MSalters    schedule 25.02.2020
comment
Я пытался использовать strand, однако, поскольку я явно не вызываю io_context.run() (пока), это не работает. Тем не менее, мне это определенно понадобится, когда я позже обработаю данные, спасибо. - person WKstraw; 25.02.2020