【问题标题】:Threads hogging up memory占用内存的线程
【发布时间】:2014-10-09 17:03:05
【问题描述】:

我在正确使用线程时遇到问题 (boost::thread)
我想让 X 线程像这样同时运行相同的功能

while( true )
    server.run();

这是我目前的编程方式,这是运行功能

void server::run()
{
    std::vector<boost::thread*> threads;
    for(std::size_t i = 0; i < threadPool; ++i)
    {
        threads.push_back( new boost::thread( boost::bind(&seed::server::pollEvent,this) ) );
    }

    for( auto & x : threads )
        x->join();

    for( auto & x : threads )
        x->detach();

    for( auto & x : threads )
        delete x;
}

但是,这会占用越来越多的 RAM,从 ~20MB 开始一直到无穷大,我的问题是,正确的方法是什么?

Minimal example of what I'm actually doing

使用 SFML 的线程时不会出现此问题。 (CPU使用率略高,但不会为每个线程占用内存,它们会被正确清除)

【问题讨论】:

  • threadPool 有多大?
  • 尝试创建一个最小的工作示例。这对潜在的帮助者非常有帮助,并且经常会在这样做时发现问题。
  • @SamMiller 4,我向你保证,这并不疯狂,应该处理得很好。
  • 我认为这与您的问题无关,但您无法从已加入的线程中分离。
  • @MichaelBurr 不,它不相关。这只是我想在此处发布之前尝试的一件事。

标签: c++ boost boost-thread


【解决方案1】:
for( unsigned int i = threads.size()-1; i > 0; i-- )
    delete threads[i];

你永远不会delete threads[0]。 for 循环条件不正确,因为 i 在循环体内永远不会变为 0。此外,这里不需要反向迭代,因为您没有删除实际元素。因此

for( auto & x : threads )
    delete x;

够了。

其次你违反了boost::thread::detach的前提条件。它说

前提条件: 线程是可连接的。

但你已经加入了他们。线程可能代表连接后的非线程(连接的后置条件):

后置条件:如果 *this 指的是进入时的执行线程,则该执行线程已完成。 *this 不再指代任何执行线程。

检查您的应用程序是否因此而创建了大量的线程可能是值得的。我不这么认为,但安全总比抱歉好。

--

最小的例子不会泄漏。 (提升 1.56.0,GCC 4.9.1,Linux)

顺便说一下,提供的最小示例无法编译,这是一个固定版本:

#include <boost/thread.hpp>
#include <vector>

class server
{
public:
    server( unsigned short int thrNum )
    {

        if( thrNum )
        threadPool = thrNum;
    };

    ~server()
    {
    };

    void run()
    {
        std::vector<boost::thread*> threads;
        for(std::size_t i = 0; i < threadPool; ++i)
        {
        threads.push_back( new boost::thread( boost::bind(&server::pollEvent,this) ) );
        }

        for( auto & x : threads )
        x->join();

        for( auto & x : threads )
        x->detach();

        for( auto & x : threads )
        delete x;
    };

private:

    void pollEvent() { return; };

    unsigned short int threadPool = 1;
};

int main()
{
    server s(4);
    while(true)
    s.run();

    return 1;
}

【讨论】:

  • 哈,抱歉,我还没有完全习惯 C++11-for 循环,但即使改变了,每次 server::run() 迭代的 ram 使用率仍然上升。
  • 我更新了我的答案,我认为还有另一个问题。见上文。
  • 危险:i 未签名,检查i&gt;=0 将导致无限循环。结束条件可以是i!=0u-1 或在未签名域for(unsigned i=0; i&gt;thread.size(); ++i) delete threads[threads.size()-1-i] 中正确循环
  • 分离并不是每个线程占用额外内存的原因。
  • 无法使用您提供的来源重现问题。问题可能出在其他地方...
【解决方案2】:

回答: 您正在线程对象上调用 detach()。这会将真正的底层线程(及其堆栈)从 boost 线程中分离出来,使其在 ->delete() 被调用时未被删除。

如果你有 c++11,为什么还要使用 boost 线程? std::thread 是可移动的,因此可以在没有 new/delete 的向量中工作。

上述最小示例的另一个迭代:

#include <thread>
#include <vector>

static const int threadPool = 4;

void noop()
{

}

void run()
{
    std::vector<std::thread> threads;
    for(std::size_t i = 0; i < threadPool; ++i)
    {
        threads.push_back( std::thread( noop ) );
    }

    for( auto & x : threads )
        x.join();
}


int main()
{
    while(true)
        run();

    return 1;
}

【讨论】:

  • 他多次表示,分离并没有改变任何东西,这是有道理的,因为底层对象已经消失,join 返回。
  • 我正在使用 boost::thread 因为 std::thread 不能完全与我的 MinGW 一起使用。并且在删除 detach 等之后仍然无法正常工作。
  • OK,然后使用std::unique_ptr&lt;boost::thread&gt;,避免使用delete。这是调试版本吗?它在发布版本中使用更少的内存吗?你这里的例子是真的吗? (即问题可能出在其他地方吗?)
  • 无论如何,我刚刚检查了 boost::thread 的文档 - 它是可移动的,因此您可以将其存储在 std::vector 中。
  • 我已经尝试过 unique_ptr、shared_ptr、用 new 分配、thread_group 等等。我也确实对此进行了研究,但一无所获。我给出的示例是我程序中的实际代码,我想指出一个事实,即它可以完美地与 SFML 线程一起工作。所以这只是一个提升问题。我也知道它是可移动的,我只是在检查是否可以通过简单的更改来解决它。
【解决方案3】:

我没有你的问题的答案,但我确实有一些我认为有趣但不适合发表评论的信息。

首先,我能够重现您在 Win7 x64 上运行的 MinGW 4.8.1 的问题,该程序构建为 32 位 x86 进程(我并不是说 x64 构建没有重现问题;我没有尝试 x64,因为我的 MinGW Boost 库仅适用于 32 位)。以防万一,我的 Boost 库是为静态链接而构建的,我使用了调试版本。

使用 MSVC 12 (Visual Studio 2013) 构建时没有重现该问题。

另外,我可以将重现问题的程序简化为以下内容,从而消除了detach()、指针和动态内存分配的所有干扰:

#include <windows.h>
#include <boost/thread.hpp>

void thread_func()
{
    // Sleep(1);
    return;
}

int main()
{
    for (;;) {
        boost::thread t1(thread_func);
        boost::thread t2(thread_func);

        t1.join();
        t2.join();
    }
}

让内存泄漏消失并不需要太多(或者至少慢到看不见的地步)。以下任何一项似乎都对我有用:

  • 取消注释对Sleep(1)的调用
  • 只使用一个线程
  • 使用两个线程,但将t1.join()移动到t2创建之前

不幸的是,使用 gbd 我没有资源来调试似乎是线程清理中的竞争条件。

【讨论】:

猜你喜欢
  • 1970-01-01
  • 2023-04-03
  • 2012-02-13
  • 1970-01-01
  • 2013-05-14
  • 1970-01-01
  • 2011-09-20
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多