【问题标题】:High CPU and Memory Consumption on using boost::asio async_read_some使用 boost::asio async_read_some 时 CPU 和内存消耗高
【发布时间】:2013-03-05 15:22:57
【问题描述】:

我已经制作了一个从客户端读取数据的服务器,并且我正在使用 boost::asio async_read_some 来读取数据,并且我已经制作了一个处理函数,这里 _ioService->poll() 将运行事件处理循环来执行准备好的处理程序.在处理程序 _handleAsyncReceive 中,我正在解除分配在 receiveDataAsync 中分配的 buf。缓冲区大小为 500。 代码如下:

bool 
TCPSocket::receiveDataAsync( unsigned int bufferSize )
{
    char *buf = new char[bufferSize + 1];

    try
    {
        _tcpSocket->async_read_some( boost::asio::buffer( (void*)buf, bufferSize ), 
                                     boost::bind(&TCPSocket::_handleAsyncReceive, 
                                                    this,
                                                    buf,
                                                    boost::asio::placeholders::error,
                                                    boost::asio::placeholders::bytes_transferred) );

            _ioService->poll();

    }
    catch (std::exception& e)
    {
        LOG_ERROR("Error Receiving Data Asynchronously");
        LOG_ERROR( e.what() );
        delete [] buf;
        return false;
    }

    //we dont delete buf here as it will be deleted by callback _handleAsyncReceive
    return true;
}


void 
TCPSocket::_handleAsyncReceive(char *buf, const boost::system::error_code& ec, size_t size)
{
    if(ec)
    {
        LOG_ERROR ("Error occurred while sending data Asynchronously.");
        LOG_ERROR ( ec.message() );
    }
    else if ( size > 0 )
    {
        buf[size] = '\0';
        LOG_DEBUG("Deleting Buffer");
        emit _asyncDataReceivedSignal( QString::fromLocal8Bit( buf ) );
    }
    delete [] buf;
}

这里的问题是,与释放相比,缓冲区的分配速度要快得多,因此内存使用率会以指数速度上升,并且在某个时间点它将消耗所有内存并且系统会卡住。 CPU 使用率也将在 90% 左右。如何减少内存和 CPU 消耗?

【问题讨论】:

    标签: sockets network-programming boost-asio asyncsocket


    【解决方案1】:

    您有内存泄漏。 io_service poll 不保证它会发送您的_handleAsyncReceive。它可以调度其他事件(例如接受),因此您在char *buf 的记忆丢失了。我猜你是从一个循环中调用receiveDataAsync,但这不是必需的——在任何情况下都会存在泄漏(泄漏速度不同)。

    如果您关注asio examples 并使用建议的模式而不是自己创建模式会更好。

    【讨论】:

      【解决方案2】:

      您可以考虑使用环绕缓冲区,也称为循环缓冲区。 Boost 有一个可用的模板循环缓冲区版本。你可以阅读它here.它背后的想法是当它变满时,它会绕到开始存储东西的地方。您也可以对其他结构或数组执行相同的操作。例如,我目前在我的应用程序中为此目的使用字节数组。

      使用专用的大型循环缓冲区来保存消息的优势在于,您不必担心为每条传入的新消息创建和删除内存。这样可以避免内存碎片,这可能会成为问题。

      要确定适当的循环缓冲区大小,您需要考虑可以进入并处于同时处理的某个阶段的最大消息数;将该数字乘以消息的平均大小,然后乘以可能为 1.5 的软糖因子。我的应用程序的平均消息大小低于 100 字节。我的缓冲区大小为 1 兆字节,这将允许至少 10,000 条消息累积而不影响环绕缓冲区。但是,如果超过 10,000 条消息确实累积而没有完全处理,那么循环缓冲区将无法使用,程序将不得不重新启动。我一直在考虑减小缓冲区的大小,因为系统可能在达到 10,000 条消息标记之前很久就死机了。

      【讨论】:

        【解决方案3】:

        正如 PSIAlt 建议的那样,考虑遵循 Boost.Asio examples 并以他们的模式为基础进行异步编程。

        不过,我建议考虑是否需要将多个读取调用排队到同一个套接字上。如果应用程序只允许单个读取操作在套接字上挂起,那么资源就会减少:

        • 不再存在io_service 中挂起的处理程序过多的情况。
        • 可以为每个读取操作预先分配和重复使用单个缓冲区。例如,下面的异步调用链只需要一个缓冲区,并允许在 Qt 信号上发出先前数据的同时并发执行启动异步读取操作,因为QString 执行深度复制。

          TCPSocket::start()
          {
            receiveDataAsync(...) --.
          }                         | 
                    .---------------'
                    |    .-----------------------------------.
                    v    v                                   |
          TCPSocket::receiveDataAsync(...)                   |
          {                                                  |
            _tcpSocket->async_read_some(_buffer); --.        |
          }                                         |        |
                    .-------------------------------'        |
                    v                                        |
          TCPSocket::_handleAsyncReceive(...)                |
          {                                                  |
            QString data = QString::fromLocal8Bit(_buffer);  |
            receiveDataAsync(...); --------------------------' 
            emit _asyncDataReceivedSignal(data);
          }
          
          ...
          
          tcp_socket.start();
          io_service.run();
          

        确定何时何地为io_service 的事件循环提供服务非常重要。通常,应用程序被设计成io_service 不会耗尽工作,而处理线程只是等待事件发生。因此,开始设置异步链,然后在更高范围内处理 io_service 事件循环是相当普遍的。

        另一方面,如果确定TCPSocket::receiveDataAsync()应该以阻塞方式处理事件循环,则考虑使用同步操作。

        【讨论】:

          猜你喜欢
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2011-02-20
          • 2010-11-05
          • 1970-01-01
          相关资源
          最近更新 更多