如何处理线程中的无限循环并避免内存泄漏

huangapple go评论61阅读模式
英文:

How to handle infinite loop in threads and avoid memory leak

问题

这段代码中存在内存泄漏的问题,因为线程a和线程b在无限循环中运行,不会释放它们所分配的资源。要解决这个问题,你可以使用std::threadjoin()函数来等待线程的完成,确保资源被正确释放。修改后的代码如下:

#include <iostream>
#include <vector>
#include <thread>
#include <chrono>
#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);

    // 等待线程a和线程b完成
    a.join();
    b.join();

    return 0;
}

通过调用join(),主线程会等待线程a和线程b执行完成,然后才会继续执行后续的代码,确保资源被正确释放,从而避免内存泄漏问题。

英文:

I have a project which run several infinite loops in threads, I simplify it to the following code:

#include &lt;iostream&gt;
#include &lt;vector&gt;
#include &lt;thread&gt;
#include &lt;boost/fiber/algo/round_robin.hpp&gt;
#include &lt;boost/thread.hpp&gt;
#include &lt;chrono&gt; 
#include &lt;boost/thread.hpp&gt;
#include &lt;string&gt;

void foo(){
    std::cout&lt;&lt;&quot;thread a&quot;&lt;&lt;std::endl;
    while(true){
        std::this_thread::sleep_for(std::chrono::seconds{5});
    }
    return;
}
void foo2(){
    std::cout&lt;&lt;&quot;thread b&quot;&lt;&lt;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;
}

It works as expected.
I use valgrind to detect memory leak and it shows it has memory leak(I guess infinite loop never release memory because it never stops). I considered to use join(), but it doesn't make sense here. I tried to add

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

before the while loop in main function, but it doesn't solve memory leak issue.

Would somebody please give me some advice how to avoid memory leak here?

答案1

得分: 2

这是一个很长的回答,所以我将从摘要开始:你示例代码中的内存泄漏不是一个问题。尽管如此,你应该修复它。修复的方法是将无限循环变为非无限循环,并加入线程。


例如,内存泄漏是这样的:

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);
    // 做一些其他事情
    
    foo_runs.store(false);
    a.join();
}
英文:

Its a long answer, so I'll start with a summary: The leak in your example code is not an issue. Nevertheless you should fix it. And the way to fix it is to turn the infinite loops into non-infinite loops and to join the threads.


A memory leak is for example this:

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

An object is dynamically allocated and when the function returns all pointers to the object are lost. The memory is still allocated to the process but you cannot free it. Calling bar many times will pile up memory until the process runs out of memory and gets killed. This is to be avoided.

Then there is a less severe type of memory leaks:

 int main() {
      bar();
 }

Here some memory is allocated, but next the process terminates. When the process terminates all memory is reclaimed by the OS. The missing delete is not such a big issue here.

There are other ways of leaking memory and I am not trying to enumerate them all, but rather use the examples to get a point across.

Then there are good reasons to worry also about this second type of leaks, that I called "less severe". And that is because it is typically not just memory that is leaked. Consider (dont write code like this! it is only for illustrating a point):

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

A is some class. In main some memory is allocated and an A is constructed. The memory is the lesser problem here. The real problem is any other resource that A claimed in its constructor. It might have opened a file. It might have opened a connection to a data base. Such resources must be cleaned up in a destructor. If the A object is not properly destroyed critical data might get lost.

Conclusion: Leaking memory when returning from main isn't a big issue. Leaking other resource is a big issue. And the memory leak is good indication that also other resources are not cleaned up properly.

In your toy example there is no problem but only a small change makes your approach problematic:

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

A is again the class that acquires some resource in its constructor and that resouce must be properly release in the destructor. Also when the program terminates you want to have the data in the database, the last log message in the log file, etc.

Rather than while(true) and detach you should use some atomic or condition variable to signal the threads that they should stop. Something along the line of

std::atomic&lt;bool&gt; 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

得分: 1

无论你做什么,你都必须在 ab 上调用 join()/detach()。如果你在主循环之前调用 join(),你将永远无法进入主循环。如果你在 main() 结束时没有调用 join()/detach(),将会调用 std::abort()

我看不到泄漏,但在 cout 流上存在竞争条件。如果分离线程 ab 逃离了 main() 并继续运行一个永不结束的函数,可能会发生潜在的泄漏。在这种情况下,线程本身会泄漏,因为它已从 *this (main) 中分离,并且没有所有者来销毁它。如果情况是这样的话,尝试在主循环后分别对 ab 调用 join()

英文:

Whatever you do, you have to join()/detach() on a and b. If you call join() before the main loop, you'll never get to the main loop. If you get to the end of main() without join()/detach(), std::abort() will be called.

I don't see a leak, but there is a race on the cout stream. Maybe potential leak can happen if detached thread a or b escapes main() and continues running a never-ending function. In such case, the thread itself is leaked since it is detached from *this (main), and there is no owner to destroy it. If that's the story, try to call join() on both a and b after the main loop.

huangapple
  • 本文由 发表于 2023年2月9日 00:33:53
  • 转载请务必保留本文链接:https://go.coder-hub.com/75388889.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定