【问题标题】:Why does Valgrind report a memory leak in this implementation?为什么 Valgrind 在这个实现中会报告内存泄漏?
【发布时间】:2018-11-11 07:31:42
【问题描述】:

我有以下一段 C++ 代码,我正在尝试执行一个例程:

#include <thread>
#include <unistd.h>
#include <sys/wait.h>
#include <memory>

using namespace std;

static void upload(const string url, const string path){ int sync_status;}

int main(){
    bool flag = true;
    for(int worker_id = 0; worker_id < 1; worker_id++){
        int worker_pid = fork();
        if (worker_pid == 0){
            while (true){
                std::unique_ptr<std::thread> uploader(nullptr);
                if (flag){       // This block only occurs once inside while loop
                    flag = false;
                    string path = "l", url = "k";
                    // Valgrind reports the next line
                    uploader = std::make_unique<std::thread>(upload, url, path);
                }
                int child_id = fork();
                if (!child_id){
                    // Do something
                    exit(0);
                }
                waitpid(child_id, NULL, 0);
                if(uploader && uploader->joinable()){
                    uploader->join();
                }
            }
            // Do something
            exit(0);
        }
    }
    return 0;
}

但 valgrind 似乎总是将这段代码报告如下:

==16424== 80 bytes in 1 blocks are definitely lost in loss record 2 of 2
==16424==    at 0x4C3017F: operator new(unsigned long) (in /usr/lib/valgrind/vgpreload_memcheck-amd64-linux.so)
==16424==    by 0x10AA39: std::unique_ptr<std::thread::_State, std::default_delete<std::thread::_State> > std::thread::_S_make_state<std::thread::_Invoker<std::tuple<void (*)(std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> >, std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> >), std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> >, std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> > > > >(std::thread::_Invoker<std::tuple<void (*)(std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> >, std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> >), std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> >, std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> > > >&&) (thread:197)
==16424==    by 0x10A04A: std::thread::thread<void (&)(std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> >, std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> >), std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> >&, std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> >&>(void (&)(std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> >, std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> >), std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> >&, std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> >&) (thread:126)
==16424==    by 0x109A64: std::_MakeUniq<std::thread>::__single_object std::make_unique<std::thread, void (&)(std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> >, std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> >), std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> >&, std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> >&>(void (&)(std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> >, std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> >), std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> >&, std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> >&) (unique_ptr.h:825)
==16424==    by 0x109514: main (main.cpp:21)

这次泄漏背后的原因是什么?我认为这是由于exit() 通话,但即使在通话之前加入unique_ptr 也无济于事。

【问题讨论】:

  • 该程序很难遵循,因为您使用std::thread、POSIX 线程和派生新进程(无论如何您似乎立即退出,所以我看不出它的意义)。您似乎也没有真正将 upload 线程用作线程,因为您启动它然后等待它完成后再执行其他任何操作,因此它并不是真正并行运行,您可以直接调用该函数。我也认为不需要将 std::thread 包装在智能指针中。
  • 这不是最小的。有几行没有做任何事情或荒谬。
  • 除了学习如何创建Minimal, Complete, and Verifiable Example,我还推荐你learn how to debug your code。但最重要的是,find a simpler problem。通过删除与您的问题无关的任何内容,您可以越来越缩小范围,直到剩下的唯一代码是重现问题的最简单代码,并且您应该希望确切地知道它发生的时间和地点以及原因。
  • 请注意,您可以使用此代码复制相同的内容:pastebin.com/QpbF5aj2
  • 除此之外:混合分叉和使用线程是你真的不想做的事情。 linuxprogrammingblog.com/…

标签: c++ multithreading memory-leaks valgrind


【解决方案1】:

你可以在[basic.start.main]找到这句话:

在不离开当前块的情况下终止程序(例如,通过调用函数std​::​exit(int) ([support.start.term]))不会破坏任何具有自动存储持续时间的对象([class.dtor])。

因此,您的 unique_ptr 的分叉副本将永远不会被销毁。这是 valgrind 报告的泄漏。

尝试销毁unique_ptr 的两个副本似乎也不是一个好主意。所以这个例子的设计可能存在更深层次的缺陷。

【讨论】:

  • 请原谅我对 C++ 的相对缺乏经验,并感谢这个惊人的推论。我在 main、exit() 和 return() 内部的地方读到过几乎提供相同功能的地方,所以对我来说肯定有很多东西要学。根据您的观点,最好的行动方案应该是什么?尽量减少变化?
  • 我真的不知道。 fork() 调用创建一个单独的进程with only a single thread。那么克隆的std::threads 现在引用原始程序中的线程会发生什么?可能没什么好事。
  • 有没有办法从 UNIX 中的子分支访问父分支的变量?
  • 至少没有简单的方法。线程和“fork”的区别在于,程序中的所有线程共享相同的内存,而fork() 创建一个独立的进程(“新程序”),拥有自己的独立内存。
猜你喜欢
  • 1970-01-01
  • 2019-03-14
  • 1970-01-01
  • 2012-07-02
  • 1970-01-01
  • 2020-09-17
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多