【问题标题】:How to handle infinite loop in threads and avoid memory leak如何处理线程中的无限循环并避免内存泄漏
【发布时间】:2023-02-09 03:25:59
【问题描述】:

我有一个在线程中运行多个无限循环的项目,我将其简化为以下代码:

#include <iostream>
#include <vector>
#include <thread>
#include <boost/fiber/algo/round_robin.hpp>
#include <boost/thread.hpp>
#include <chrono> 
#include <boost/thread.hpp>
#include <string>

void foo(){
    std::cout<<"thread a"<<std::endl;
    while(true){
        std::this_thread::sleep_for(std::chrono::seconds{5});
    }
    return;
}
void foo2(){
    std::cout<<"thread b"<<std::endl;
    while(true){
        std::this_thread::sleep_for(std::chrono::seconds{5});
    }
    return;
}

int main(){
    std::thread a(foo);
    std::thread b(foo2);
    while(true){
        std::this_thread::sleep_for(std::chrono::seconds{5});
    }
    return 0;
}

它按预期工作。 我使用 valgrind 来检测内存泄漏,它显示它有内存泄漏(我猜无限循环永远不会释放内存,因为它永远不会停止)。我考虑过使用 join(),但在这里没有意义。我试图添加

a.detach();
b.detach();

在 main 函数的 while 循环之前,但它没有解决内存泄漏问题。

有人可以给我一些建议如何避免内存泄漏吗?

【问题讨论】:

  • 当您的程序无法以“正常”方式终止时,为什么还要担心泄漏?我确信您并不真的需要无法连接的线程。你的实际目标是什么?
  • 向您的线程发出停止信号,即使用 stop_tokenatomic&lt;bool&gt;,然后使用 join()
  • 要点:您不需要foofoo2 中的那些return; 语句。
  • “他们永远不应该停止”,我认为你应该重新考虑这一点。即使是 24/7 运行的代码最终也必须在某个时刻终止。使线程可连接,然后可能不仅这个问题而且其他问题都会消失
  • 分离线程很少是正确的解决方案。实际上,从长远来看,每当我认为我应该分离一个线程时,结果证明这是一个严重的错误

标签: c++ multithreading memory-leaks


【解决方案1】:

这是一个很长的答案,所以我将从一个总结开始:示例代码中的泄漏不是问题。不过你应该修复它。修复它的方法是将无限循环变成非无限循环并加入线程。


内存泄漏例如是这样的:

void bar() {
    int * x = new int;
}

对象是动态分配的,当函数返回时,指向该对象的所有指针都将丢失。内存仍分配给该进程,但您无法释放它。多次调用 bar 会堆积内存,直到进程耗尽内存并被杀死。这是要避免的。

然后是一种不太严重的内存泄漏类型:

 int main() {
      bar();
 }

这里分配了一些内存,但接下来进程终止。当进程终止时,所有内存都由操作系统回收。丢失的delete在这里不是什么大问题。

那么也有充分的理由担心第二种类型的泄漏,我称之为“不太严重”。那是因为泄漏的通常不仅仅是内存。考虑(不要写这样的代码!它只是为了说明一点):

   int main() {
       A* = new A();
   }

A 是某个班级。在main 中分配了一些内存并构造了一个A。内存是这里的小问题。真正的问题是A 在其构造函数中声明的任何其他资源。它可能打开了一个文件。它可能已经打开了与数据库的连接。此类资源必须在析构函数中清理。如果 A 对象没有被正确销毁,关键数据可能会丢失。

结论:从main 返回时内存泄漏不是大问题。泄漏其他资源是一个大问题。内存泄漏很好地表明其他资源也没有正确清理。

在您的玩具示例中没有问题,但只有很小的变化会使您的方法有问题:

void foo(){
    A a;
    while(true){
        std::this_thread::sleep_for(std::chrono::seconds{5});
    }
}

A 再次是在其构造函数中获取某些资源的类,并且必须在析构函数中正确释放该资源。此外,当程序终止时,您希望数据库中有数据、日志文件中的最后一条日志消息等。

而不是 while(true)detach 你应该使用一些原子或条件变量来通知线程它们应该停止。沿线的东西

std::atomic<bool> foo_runs;

void foo(){
    A a;
    while(foo_runs.load()){
        std::this_thread::sleep_for(std::chrono::seconds{5});
    }
}

int main() {
    foo_runs.store(true);
    std::thread a(foo);
    // do something else 
    
    foo_runs.store(false);
    a.join();
}

【讨论】:

  • 太感谢了。它有很大帮助!
  • 澄清一下,有更多的方法可以导致内存泄漏。一般定义是程序逻辑不再可访问的任何内存。内存泄漏的其他示例:强引用循环、使某些对象的某些实例变得虚无缥缈的逻辑错误。
【解决方案2】:

无论你做什么,你都必须在abjoin()/detach()。如果您在主循环之前调用join(),您将永远无法进入主循环。如果在没有join()/detach()的情况下到达main()的结尾,将调用std::abort()

我没有看到泄漏,但 cout 流上有一场比赛。如果分离线程 ab 转义 main() 并继续运行一个永无止境的函数,可能会发生潜在的泄漏。在这种情况下,线程本身是泄漏的,因为它与*this(主)分离,并且没有所有者可以销毁它。如果是这样,请尝试在主循环之后同时在ab 上调用join()

【讨论】:

    猜你喜欢
    • 2019-06-29
    • 1970-01-01
    • 1970-01-01
    • 2012-02-12
    • 1970-01-01
    • 2021-03-05
    • 2020-05-13
    相关资源
    最近更新 更多