Проблема
Я использую boost::asio для проекта где два процесса на одном компьютере взаимодействуют с помощью TCP/IP. Один генерирует данные для чтения другим, но я сталкиваюсь с проблемой, когда данные периодически не отправляются через соединение. Я свел это к очень простому примеру ниже, основанному на пример асинхронного эхо-сервера TCP.
Процессы (исходный код ниже) начинаются нормально, доставляя данные с высокой скоростью от отправителя к получателю. Затем внезапно данные вообще не доставляются в течение примерно пяти секунд. Затем данные доставляются снова до следующей необъяснимой паузы. В течение этих пяти секунд процессы потребляют 0% ЦП, и никакие другие процессы, похоже, ничего особенного не делают. Пауза всегда одинаковая – пять секунд.
Я пытаюсь выяснить, как избавиться от этих киосков и что их вызывает.
Использование ЦП во время всего запуска:
Обратите внимание на три провала использования ЦП в середине выполнения — «выполнение» — это один вызов серверного процесса и клиентского процесса. Во время этих провалов данные не доставлялись. Количество провалов и их время различаются в зависимости от прогона - иногда вообще нет провалов, иногда много.
Я могу повлиять на «вероятность» этих остановок, изменив размер буфера чтения — например, если я сделаю буфер чтения кратным размеру отправляемого фрагмента, эта проблема почти исчезнет. далеко, но не совсем.
Источник и описание теста
Я скомпилировал приведенный ниже код с помощью Visual Studio 2005, используя Boost 1.43 и Boost 1.45. Я тестировал 64-разрядную версию Windows Vista (на четырехъядерном процессоре) и 64-разрядную версию Windows 7 (как на четырехъядерном, так и на двухъядерном процессоре).
Сервер принимает соединение, а затем просто считывает и отбрасывает данные. Всякий раз, когда выполняется чтение, выдается новое чтение.
Клиент подключается к серверу, затем помещает пакет пакетов в очередь отправки. После этого он записывает пакеты по одному. Всякий раз, когда запись завершается, записывается следующий пакет в очереди. Отдельный поток отслеживает размер очереди и выводит его на стандартный вывод каждую секунду. Во время остановок ввода-вывода размер очереди остается точно таким же.
Я пробовал использовать scatter io (запись нескольких пакетов за один системный вызов), но результат тот же. Если я отключу порты завершения ввода-вывода в Boost с помощью BOOST_ASIO_DISABLE_IOCP
, проблема исчезнет, но ценой значительного снижения пропускной способности.
// Example is adapted from async_tcp_echo_server.cpp which is
// Copyright (c) 2003-2010 Christopher M. Kohlhoff (chris at kohlhoff dot com)
//
// Start program with -s to start as the server
#ifndef _WIN32_WINNT
#define _WIN32_WINNT 0x0501
#endif
#include <iostream>
#include <tchar.h>
#include <boost/asio.hpp>
#include <boost/bind.hpp>
#include <boost/thread.hpp>
#define PORT "1234"
using namespace boost::asio::ip;
using namespace boost::system;
class session {
public:
session(boost::asio::io_service& io_service) : socket_(io_service) {}
void do_read() {
socket_.async_read_some(boost::asio::buffer(data_, max_length),
boost::bind(&session::handle_read, this, _1, _2));
}
boost::asio::ip::tcp::socket& socket() { return socket_; }
protected:
void handle_read(const error_code& ec, size_t bytes_transferred) {
if (!ec) {
do_read();
} else {
delete this;
}
}
private:
tcp::socket socket_;
enum { max_length = 1024 };
char data_[max_length];
};
class server {
public:
explicit server(boost::asio::io_service& io_service)
: io_service_(io_service)
, acceptor_(io_service, tcp::endpoint(tcp::v4(), atoi(PORT)))
{
session* new_session = new session(io_service_);
acceptor_.async_accept(new_session->socket(),
boost::bind(&server::handle_accept, this, new_session, _1));
}
void handle_accept(session* new_session, const error_code& ec) {
if (!ec) {
new_session->do_read();
new_session = new session(io_service_);
acceptor_.async_accept(new_session->socket(),
boost::bind(&server::handle_accept, this, new_session, _1));
} else {
delete new_session;
}
}
private:
boost::asio::io_service& io_service_;
boost::asio::ip::tcp::acceptor acceptor_;
};
class client {
public:
explicit client(boost::asio::io_service &io_service)
: io_service_(io_service)
, socket_(io_service)
, work_(new boost::asio::io_service::work(io_service))
{
io_service_.post(boost::bind(&client::do_init, this));
}
~client() {
packet_thread_.join();
}
protected:
void do_init() {
// Connect to the server
tcp::resolver resolver(io_service_);
tcp::resolver::query query(tcp::v4(), "localhost", PORT);
tcp::resolver::iterator iterator = resolver.resolve(query);
socket_.connect(*iterator);
// Start packet generation thread
packet_thread_.swap(boost::thread(
boost::bind(&client::generate_packets, this, 8000, 5000000)));
}
typedef std::vector<unsigned char> packet_type;
typedef boost::shared_ptr<packet_type> packet_ptr;
void generate_packets(long packet_size, long num_packets) {
// Add a single dummy packet multiple times, then start writing
packet_ptr buf(new packet_type(packet_size, 0));
write_queue_.insert(write_queue_.end(), num_packets, buf);
queue_size = num_packets;
do_write_nolock();
// Wait until all packets are sent.
while (long queued = InterlockedExchangeAdd(&queue_size, 0)) {
std::cout << "Queue size: " << queued << std::endl;
Sleep(1000);
}
// Exit from run(), ignoring socket shutdown
work_.reset();
}
void do_write_nolock() {
const packet_ptr &p = write_queue_.front();
async_write(socket_, boost::asio::buffer(&(*p)[0], p->size()),
boost::bind(&client::on_write, this, _1));
}
void on_write(const error_code &ec) {
if (ec) { throw system_error(ec); }
write_queue_.pop_front();
if (InterlockedDecrement(&queue_size)) {
do_write_nolock();
}
}
private:
boost::asio::io_service &io_service_;
tcp::socket socket_;
boost::shared_ptr<boost::asio::io_service::work> work_;
long queue_size;
std::list<packet_ptr> write_queue_;
boost::thread packet_thread_;
};
int _tmain(int argc, _TCHAR* argv[]) {
try {
boost::asio::io_service io_svc;
bool is_server = argc > 1 && 0 == _tcsicmp(argv[1], _T("-s"));
std::auto_ptr<server> s(is_server ? new server(io_svc) : 0);
std::auto_ptr<client> c(is_server ? 0 : new client(io_svc));
io_svc.run();
} catch (std::exception& e) {
std::cerr << "Exception: " << e.what() << "\n";
}
return 0;
}
Итак, мой вопрос в основном:
Как мне избавиться от этих прилавков?
Почему это происходит?
Обновление: кажется, существует некоторая корреляция с активностью диска, противоречащая тому, что я сказал выше, поэтому похоже, что если я запущу большую копию каталога на диск во время выполнения теста, это может увеличить частоту ио зависает. Это может означать, что срабатывает приоритет ввода-вывода Windows? Поскольку паузы всегда имеют одинаковую длину, это звучит как тайм-аут где-то в коде ввода-вывода ОС...