2013-03-05 370 views
3

我已经做了一个服务器,从客户端读取数据,我使用boost :: asio async_read_some读取数据,并且我已经创建了一个处理函数,并且在这里_ioService-> poll()将运行事件处理循环来执行就绪处理程序。在处理程序_handleAsyncReceive中,我取消了在receiveDataAsync中分配的buf。缓冲区大小为500 代码如下:使用boost :: asio时的高CPU和内存消耗async_read_some

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消耗?

回答

2

您有内存泄漏。 io_service民意调查不保证它与调度你的_handleAsyncReceive。它可以派遣其他事件(例如接受),因此你的记忆在char *buf丢失。我猜你从回路呼叫receiveDataAsync,但它不是必需的 - 任何情况下泄漏都会存在(具有不同的泄漏速度)。

它更好,如果你按照asio examples并使用建议的模式,而不是自己做。

2

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

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

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

2

由于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不会耗尽工作,并且处理线程只是等待事件发生。因此,开始设置异步链是相当常见的,然后在更高的范围内处理事件循环。

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