【问题标题】:Boost::asio and boost::bind: Functor memory is never releasedBoost::asio 和 boost::bind: Functor 内存永远不会被释放
【发布时间】:2016-04-28 11:22:58
【问题描述】:

我的代码正在分配内存并且从不释放它,即使它应该(至少在我看来)。

标题如下所示:

typedef boost::asio::ssl::stream<boost::asio::ip::tcp::socket> sslSocket_t;

class Object {
    boost::asio::io_service ioService_;
    boost::asio::ip::tcp::acceptor acceptor_;
    boost::asio::ssl::context context_;

    void functionOne();
    void functionTwo(shared_ptr<sslSocket_t>& sslSocket, const boost::system::error_code& error)
}

我的来源是这样的:

void Object::functionOne() {
    for (int i = 0; i < 10; i++) {
        shared_ptr<sslSocket_t> sslSocket(new sslSocket_t(ioService_, context_));
        acceptor_.async_accept(sslSocket->lowest_layer(),
                       boost::bind(&Object::functionTwo, this, sslSocket, boost::asio::placeholders::error));
    }
    acceptor_.cancel();

    boost::asio::io_service::work work(ioService_);
    ioService_.run();
}

void functionTwo(shared_ptr<sslSocket_t>& sslSocket, const boost::system::error_code& err) {
    // Do nothing
}

所以当我调用 Object.functionOne() 时,内存被分配给 Object.ioService_ 对象,以便能够调用绑定的异步方法。然后在循环之后,接受器上所有未决的异步操作都将被取消。只要 Object.ioService_.run() 被调用,相应的处理程序就会被调用(我一直在测试它)。但是由于某种原因,分配的内存没有被释放。那么有人可以解释一下为什么内存没有被释放并给我一个提示如何释放它?

顺便说一句:我正在开发 debian 并查看 /proc/self/status -> VmRSS 以了解使用的内存。

@Vinnie Falco

#include <boost/asio.hpp>
#include <boost/asio/ssl.hpp>
#include <boost/bind.hpp>

#include <iostream>
#include <memory>

typedef boost::asio::ssl::stream<boost::asio::ip::tcp::socket> sslSocket_t;

using namespace std;

struct T  {

    boost::asio::io_service ioService_;
    boost::asio::ip::tcp::acceptor acceptor_;
    boost::asio::ssl::context context_;

    void functionOne() {
        for (int i = 0; i < 10; i++) {
            shared_ptr<sslSocket_t> sslSocket(new sslSocket_t(ioService_, context_));
            acceptor_.async_accept(sslSocket->lowest_layer(),
                               boost::bind(&T::functionTwo, this, sslSocket, boost::asio::placeholders::error));
        }
        acceptor_.cancel();

        boost::asio::io_service::work work(ioService_);
        ioService_.run();
    }

    void functionTwo(shared_ptr<sslSocket_t>& sslSocket, const boost::system::error_code& err) {
        // Do nothing
    }

    T() : acceptor_(ioService_,
                    boost::asio::ip::tcp::endpoint(boost::asio::ip::tcp::v4(), 443)),
          context_(boost::asio::ssl::context::sslv23_server)  {

    }

    ~T()  {

    }
};

int main()  {

    try  {
        T t;

        t.functionOne();
    } catch (std::exception& e) {
        cout << "Exception: " << e.what() << endl;
  }
}

我的问题不是,是否以及为什么调用 T 的析构函数,这按预期工作。但是关于已用内存的行为很奇怪。 因此,如果您增加 for 循环中的限制,您将观察到,程序保留了大量内存,即使在调用所有异步处理程序后应该释放它。但是 sslSocket 对象没有被释放,这就是我的问题所在:为什么内存(特别是为 sslSocket 分配的内存)绑定到函子 functionTwo ,即使在调用异步方法 fucntionTwo 并且没有留下对 sslSocket 的引用之后,也不会被释放?

我最终解释我的担忧的方法(4 月 28 日编辑)

好的,我做了一个可运行的例子,这表明了我的担忧: My Problem in an example

输出:

Before leaking call:     6984 kB
Asynchronous calls of functionTwo: 10000
Memory while ioService is still running:   460244 kB
Memory after ioService is stopped:   460244 kB

更疯狂的是,在我自己的本地实现中,我得到以下输出:

Memory leaking call:     8352 kB
Asynchronous calls of functionTwo: 10000
Memory while ioService is still running:   471932 kB
Memory after ioService is stopped:     8436 kB

因此可以清楚地看到:即使调用了所有异步操作,内存也没有被释放。

总结和理解(?)行为(最后编辑)

正如你们中的一些人可能误解了,我不认为我的代码中存在某种泄漏。我在我的代码示例中将结构命名为 Leak,这可能会让您感到困惑,但我的问题不是我的示例中是否发生内存泄漏以及在何处发生内存泄漏。这是关于结合 ioService 对象的内存分配。首先我认为,声称的内存正在无限增加。我做了最后一种方法来理解这种行为并得出结论,内存管理很好。内存不会被操作系统回收,但程序的内存分配正在收敛到一个限制,这对我来说很好。所以这个问题不在我的范围内。

Example: Converging memory consumption

最让我不安的是,我的本地实现表现出略有不同的行为。当 ioService 对象完成其工作并重置时,操作系统回收了内存,这符合我的期望。

所以总结所有观察:

分配的内存由 C++ 运行时和操作系统管理。直接观察分配过程非常困难(即使不是不可能?),因为它经过优化以减少对新内存页面的请求量,这意味着分配和释放的内存可能不会立即重新分配由操作系统。

为了向我指出这种行为的关键点,我想描述一下我的程序的用法:我正在开发一个服务器应用程序,这意味着程序应该运行无限长的时间。如果程序在某个时间要求大量的峰值内存,那完全没问题,但它需要在运行时的某个时间点释放所要求的内存,而不是在运行时之后。所以对我来说,只剩下一个问题了:

操作系统是否会在某个时候回收已声明的(但未使用的)内存?还是我必须在运行时自己管理内存(使用 newdelete)?

【问题讨论】:

  • 这个服务器接受了多少个连接?
  • @Nim 无。这只是消耗的内存,只需构造 sslSocket 并添加异步操作即可。
  • 好吧 - 处理程序仅在有连接时才执行,否则它只会保留套接字直到有接受 - 无论如何 - 请参阅我的答案 - 您使用 asio 和异步操作的方法是坏了..
  • @Nim 自从我调用 acceptor.cancel() 后处理程序被执行,所以所有待处理的操作都会在 ioService_.run() 后立即中止i> 被调用。是的,我也实施了典型的方法,我的例子只是为了突出我的问题。谢谢你的耐心:-)
  • ..ahh - 错过了,是的 - 它应该被中止。您能否将 ssl 套接字包装在一个简单的类中,并查看该类的析构函数是否被触发。如果某个共享指针的某处有悬空引用,我会感到非常惊讶......

标签: c++ memory boost boost-asio boost-bind


【解决方案1】:

我不确定问题出在哪里,但我认为你做错了什么。您能否提供一个展示该问题的独立示例?本示例程序编译运行,调用析构函数:

#include <boost/asio.hpp>
#include <functional>
#include <iostream>
#include <memory>

struct T
{
    T()
    {
        std::cerr << "T::T()\n";
    }

    ~T()
    {
        std::cerr << "T::~T()\n";
    }
};

void f(std::shared_ptr<T>&)
{
}

int main()
{
    using namespace boost::asio;
    io_service ios;
    ios.post(std::bind(&f, std::make_shared<T>()));
    ios.run();
}

你可以在这里看到输出:http://melpon.org/wandbox/permlink/0fkIAnoMXDOeedx7

输出:

T::T()
T::~T()

【讨论】:

    【解决方案2】:

    我花了一段时间,但我终于设法自己解决了。

    所以为了澄清事情,让我们确保了解我的问题的根源: 我正在开发一个服务器应用程序,它可以运行无限长的时间。此应用程序必须能够处理大量并发传入连接。在某个时间点,可能会出现负载高峰,导致我的应用程序占用大量内存。然后过了一段时间,大部分传入的请求都被处理了,导致很多对象在运行时被释放。由于操作系统不需要内存(我的应用程序是服务器上唯一的巨大内存消耗者),所有释放的内存都保留在我的应用程序中,并且可以在另一个时间点重用。这对我来说绝对没问题,但一些客户和管理员可能会将不断声明的大量内存误解为内存泄漏应用程序。为了避免这种情况,我想手动将一些未使用的内存交还给操作系统。在我的示例中,绑定到 ioService 的处理程序(例如接受新连接)将在运行时释放,但操作系统不会回收相应的内存。所以要手动执行此操作,我找到了以下解决方案:

    在 C/C++ 中释放 Linux 下未使用的堆内存:int malloc_trim(size_t pad)

    可以在here 找到文档。简化解释,此方法将未使用的内存从堆释放到操作系统,这正是我一直在寻找的。我知道在内存优化方面,手动使用这个功能可能很危险,但由于我只想每隔几分钟释放一次内存,这个性能问题我可以接受。

    感谢大家的努力和耐心!

    【讨论】:

      【解决方案3】:

      以您的自包含示例并在 valgrind 下运行它表明确实没有任何被泄露

      ==14098== 
      ==14098== HEAP SUMMARY:
      ==14098==     in use at exit: 73,696 bytes in 7 blocks
      ==14098==   total heap usage: 163,524 allocs, 163,517 frees, 733,133,505 bytes allocated
      ==14098== 
      ==14098== LEAK SUMMARY:
      ==14098==    definitely lost: 0 bytes in 0 blocks
      ==14098==    indirectly lost: 0 bytes in 0 blocks
      ==14098==      possibly lost: 0 bytes in 0 blocks
      ==14098==    still reachable: 73,696 bytes in 7 blocks
      ==14098==         suppressed: 0 bytes in 0 blocks
      ==14098== Rerun with --leak-check=full to see details of leaked memory
      

      如果您提供valgrind --show-leak-kinds=all --leak-check=full ./test,您会发现“泄漏”(剩余可访问)是从 libssl/libcrypto 分配的常见静态内容。即使您只进行 1 次迭代,它们也会被分配。 10000 次迭代没有变化。

      ==14214== Memcheck, a memory error detector
      ==14214== Copyright (C) 2002-2015, and GNU GPL'd, by Julian Seward et al.
      ==14214== Using Valgrind-3.11.0 and LibVEX; rerun with -h for copyright info
      ==14214== Command: ./test 10000
      ==14214== 
      Before leaking call:    50056 kB
      Asynchronous calls of functionTwo: 10000
      Memory while ioService is still running:   265592 kB
      Memory after ioService is stopped:   265592 kB
      ==14214== 
      ==14214== HEAP SUMMARY:
      ==14214==     in use at exit: 73,696 bytes in 7 blocks
      ==14214==   total heap usage: 163,524 allocs, 163,517 frees, 733,133,505 bytes allocated
      ==14214== 
      ==14214== 24 bytes in 1 blocks are still reachable in loss record 1 of 7
      ==14214==    at 0x4C2BBCF: malloc (in /usr/lib/valgrind/vgpreload_memcheck-amd64-linux.so)
      ==14214==    by 0x5307E77: CRYPTO_malloc (in /lib/x86_64-linux-gnu/libcrypto.so.1.0.0)
      ==14214==    by 0x53BF315: lh_insert (in /lib/x86_64-linux-gnu/libcrypto.so.1.0.0)
      ==14214==    by 0x53C1863: ??? (in /lib/x86_64-linux-gnu/libcrypto.so.1.0.0)
      ==14214==    by 0x53C245D: ERR_get_state (in /lib/x86_64-linux-gnu/libcrypto.so.1.0.0)
      ==14214==    by 0x53C25EE: ERR_clear_error (in /lib/x86_64-linux-gnu/libcrypto.so.1.0.0)
      ==14214==    by 0x40C9CA: context (context.ipp:70)
      ==14214==    by 0x40C9CA: Leak::Leak() (test.cpp:77)
      ==14214==    by 0x403E13: main (test.cpp:86)
      ==14214== 
      ==14214== 32 bytes in 1 blocks are still reachable in loss record 2 of 7
      ==14214==    at 0x4C2BBCF: malloc (in /usr/lib/valgrind/vgpreload_memcheck-amd64-linux.so)
      ==14214==    by 0x5307E77: CRYPTO_malloc (in /lib/x86_64-linux-gnu/libcrypto.so.1.0.0)
      ==14214==    by 0x53BE7AE: sk_new (in /lib/x86_64-linux-gnu/libcrypto.so.1.0.0)
      ==14214==    by 0x507FD69: ??? (in /lib/x86_64-linux-gnu/libssl.so.1.0.0)
      ==14214==    by 0x5081E68: SSL_COMP_get_compression_methods (in /lib/x86_64-linux-gnu/libssl.so.1.0.0)
      ==14214==    by 0x5087532: SSL_library_init (in /lib/x86_64-linux-gnu/libssl.so.1.0.0)
      ==14214==    by 0x40B9A8: do_init (openssl_init.ipp:39)
      ==14214==    by 0x40B9A8: boost::asio::ssl::detail::openssl_init_base::instance() (openssl_init.ipp:131)
      ==14214==    by 0x403C3C: openssl_init (openssl_init.hpp:60)
      ==14214==    by 0x403C3C: __static_initialization_and_destruction_0 (openssl_init.hpp:90)
      ==14214==    by 0x403C3C: _GLOBAL__sub_I_count (test.cpp:96)
      ==14214==    by 0x40FE1C: __libc_csu_init (in /home/sehe/Projects/stackoverflow/test)
      ==14214==    by 0x5EC09CE: (below main) (libc-start.c:245)
      ==14214== 
      ==14214== 32 bytes in 1 blocks are still reachable in loss record 3 of 7
      ==14214==    at 0x4C2BBCF: malloc (in /usr/lib/valgrind/vgpreload_memcheck-amd64-linux.so)
      ==14214==    by 0x5307E77: CRYPTO_malloc (in /lib/x86_64-linux-gnu/libcrypto.so.1.0.0)
      ==14214==    by 0x53BE7CC: sk_new (in /lib/x86_64-linux-gnu/libcrypto.so.1.0.0)
      ==14214==    by 0x507FD69: ??? (in /lib/x86_64-linux-gnu/libssl.so.1.0.0)
      ==14214==    by 0x5081E68: SSL_COMP_get_compression_methods (in /lib/x86_64-linux-gnu/libssl.so.1.0.0)
      ==14214==    by 0x5087532: SSL_library_init (in /lib/x86_64-linux-gnu/libssl.so.1.0.0)
      ==14214==    by 0x40B9A8: do_init (openssl_init.ipp:39)
      ==14214==    by 0x40B9A8: boost::asio::ssl::detail::openssl_init_base::instance() (openssl_init.ipp:131)
      ==14214==    by 0x403C3C: openssl_init (openssl_init.hpp:60)
      ==14214==    by 0x403C3C: __static_initialization_and_destruction_0 (openssl_init.hpp:90)
      ==14214==    by 0x403C3C: _GLOBAL__sub_I_count (test.cpp:96)
      ==14214==    by 0x40FE1C: __libc_csu_init (in /home/sehe/Projects/stackoverflow/test)
      ==14214==    by 0x5EC09CE: (below main) (libc-start.c:245)
      ==14214== 
      ==14214== 128 bytes in 1 blocks are still reachable in loss record 4 of 7
      ==14214==    at 0x4C2BBCF: malloc (in /usr/lib/valgrind/vgpreload_memcheck-amd64-linux.so)
      ==14214==    by 0x5307E77: CRYPTO_malloc (in /lib/x86_64-linux-gnu/libcrypto.so.1.0.0)
      ==14214==    by 0x53BEFE1: lh_new (in /lib/x86_64-linux-gnu/libcrypto.so.1.0.0)
      ==14214==    by 0x53C1512: ??? (in /lib/x86_64-linux-gnu/libcrypto.so.1.0.0)
      ==14214==    by 0x53C182F: ??? (in /lib/x86_64-linux-gnu/libcrypto.so.1.0.0)
      ==14214==    by 0x53C245D: ERR_get_state (in /lib/x86_64-linux-gnu/libcrypto.so.1.0.0)
      ==14214==    by 0x53C25EE: ERR_clear_error (in /lib/x86_64-linux-gnu/libcrypto.so.1.0.0)
      ==14214==    by 0x40C9CA: context (context.ipp:70)
      ==14214==    by 0x40C9CA: Leak::Leak() (test.cpp:77)
      ==14214==    by 0x403E13: main (test.cpp:86)
      ==14214== 
      ==14214== 176 bytes in 1 blocks are still reachable in loss record 5 of 7
      ==14214==    at 0x4C2BBCF: malloc (in /usr/lib/valgrind/vgpreload_memcheck-amd64-linux.so)
      ==14214==    by 0x5307E77: CRYPTO_malloc (in /lib/x86_64-linux-gnu/libcrypto.so.1.0.0)
      ==14214==    by 0x53BEFBF: lh_new (in /lib/x86_64-linux-gnu/libcrypto.so.1.0.0)
      ==14214==    by 0x53C1512: ??? (in /lib/x86_64-linux-gnu/libcrypto.so.1.0.0)
      ==14214==    by 0x53C182F: ??? (in /lib/x86_64-linux-gnu/libcrypto.so.1.0.0)
      ==14214==    by 0x53C245D: ERR_get_state (in /lib/x86_64-linux-gnu/libcrypto.so.1.0.0)
      ==14214==    by 0x53C25EE: ERR_clear_error (in /lib/x86_64-linux-gnu/libcrypto.so.1.0.0)
      ==14214==    by 0x40C9CA: context (context.ipp:70)
      ==14214==    by 0x40C9CA: Leak::Leak() (test.cpp:77)
      ==14214==    by 0x403E13: main (test.cpp:86)
      ==14214== 
      ==14214== 600 bytes in 1 blocks are still reachable in loss record 6 of 7
      ==14214==    at 0x4C2BBCF: malloc (in /usr/lib/valgrind/vgpreload_memcheck-amd64-linux.so)
      ==14214==    by 0x5307E77: CRYPTO_malloc (in /lib/x86_64-linux-gnu/libcrypto.so.1.0.0)
      ==14214==    by 0x53C23F5: ERR_get_state (in /lib/x86_64-linux-gnu/libcrypto.so.1.0.0)
      ==14214==    by 0x53C25EE: ERR_clear_error (in /lib/x86_64-linux-gnu/libcrypto.so.1.0.0)
      ==14214==    by 0x40C9CA: context (context.ipp:70)
      ==14214==    by 0x40C9CA: Leak::Leak() (test.cpp:77)
      ==14214==    by 0x403E13: main (test.cpp:86)
      ==14214== 
      ==14214== 72,704 bytes in 1 blocks are still reachable in loss record 7 of 7
      ==14214==    at 0x4C2BBCF: malloc (in /usr/lib/valgrind/vgpreload_memcheck-amd64-linux.so)
      ==14214==    by 0x57731FF: ??? (in /usr/lib/x86_64-linux-gnu/libstdc++.so.6.0.21)
      ==14214==    by 0x4010609: call_init.part.0 (dl-init.c:72)
      ==14214==    by 0x401071A: call_init (dl-init.c:30)
      ==14214==    by 0x401071A: _dl_init (dl-init.c:120)
      ==14214==    by 0x4000D09: ??? (in /lib/x86_64-linux-gnu/ld-2.21.so)
      ==14214==    by 0x1: ???
      ==14214==    by 0xFFEFFFF76: ???
      ==14214==    by 0xFFEFFFF7D: ???
      ==14214== 
      ==14214== LEAK SUMMARY:
      ==14214==    definitely lost: 0 bytes in 0 blocks
      ==14214==    indirectly lost: 0 bytes in 0 blocks
      ==14214==      possibly lost: 0 bytes in 0 blocks
      ==14214==    still reachable: 73,696 bytes in 7 blocks
      ==14214==         suppressed: 0 bytes in 0 blocks
      ==14214== 
      ==14214== For counts of detected and suppressed errors, rerun with: -v
      ==14214== ERROR SUMMARY: 0 errors from 0 contexts (suppressed: 0 from 0)
      

      您测量内存使用情况的方法不正确。

      【讨论】:

      • 感谢您的努力,但正如我在最后总结中提到的,我不是在寻找某种泄漏,而是我试图了解内存分配和释放如何与ioService 对象有效。
      【解决方案4】:

      我认为你的方法是错误的。您永远不应该将异步操作与 asio 交错。如果您这样做,就会发生各种未定义的废话。你通常实现异步接受的方式如下:

      void do_accept() {
        shared_ptr<sslSocket_t> socket(new sslSocket_t(ioService_, context_));
        // Queue an async accept operation
        acceptor_.async_accept(socket->lowest_layer(), [this, socket](auto ec) {
          if (!ec) {
            // Handle the socket
          }
          // If not shutting down
          this->do_accept(); // next accept
        });
      }
      

      【讨论】:

      • 我不是那个给你 -1 的人,但我想说你不应该交错异步操作的大胆声明是不正确的。你可以随意交错;只是不能保证它们的执行顺序(实际上,可能是并发的)。在某些设计中,这可能非常好。其他的,没那么多。
      • @KazDragon - 在一个套接字上,当文档说以下内容时,我不确定我是否同意您的看法(例如,对于 async_read):“此操作以零个或多个调用流的 async_read_some 函数,称为组合操作。程序必须确保流不执行其他读取操作(例如 async_read、流的 async_read_some 函数或任何其他执行读取的组合操作),直到此操作完成. "
      • @KazDragon,我想说对于accept,从文档中不清楚交错是否安全,但对于其他操作(例如写入,同样的原则适用。)但是你可以混合读取/写操作 - 但这是给定的..
      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2023-04-07
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多