请教一个 shared_ptr 内存泄漏的问题 - V2EX
V2EX = way to explore
V2EX 是一个关于分享和探索的地方
现在注册
已注册用户请  登录
请不要在回答技术问题时复制粘贴 AI 生成的内容
guang19
V2EX    程序员

请教一个 shared_ptr 内存泄漏的问题

  •  
  •   guang19 2022-05-10 11:19:39 +08:00 2477 次点击
    这是一个创建于 1252 天前的主题,其中的信息可能已经有所发展或是发生改变。

    如题,我正在用 cpp 写一个线程池的 submit 函数,为了可以让用户可以异步的获取到函数执行的返回值,我使用了 future 和 promise 特性,一部分代码如下:

    template <typename Fn, typename... Args, typename HasRet, typename Redundant> ::std::future<typename ::std::result_of<Fn(Args...)>::type> ThreadPool::submit(Fn&& func, Args... args) { if (!running_) { LOGS_FATAL << "thread pool has not been initialized"; } using RetType = typename ::std::result_of<Fn(Args...)>::type; LockGuardType lock(mutex_); auto promise = ::std::make_shared<::std::promise<RetType>>(); auto future = promise->get_future(); ::std::function<RetType()> tmpTask = ::std::bind(::std::forward<Fn>(func), ::std::forward<Args>(args)...); 1 ) TaskType task = [t = ::std::move(tmpTask), promise]() mutable { 2 ) assert(promise.use_count() == 1); promise->set_value(t()); }; 3 ) assert(promise.use_count() == 2); ...... return future; } 

    1 )处我用的 lambda 是值捕获 promise,3 )处的 use_count 总为 2 这没问题,现在的问题不知为什么 2 )处的 use_count 却不稳定,有时为 3 ,有时为 1 ? 下面为测试代码:

    TEST_F(ThreadPoolTest, SubmitHasRetval) { ::std::function<int32_t ()> f = []() -> int32_t { return 5; }; auto future1 = threadPool->submit(f); future1.wait(); ASSERT_EQ(future1.get(), 5); auto future2 = threadPool->submit(::std::move(f)); future2.wait(); ASSERT_EQ(future2.get(), 5); } 

    debug 模式下正常结果为:

    2022-05-10 11:09:44.942 [9924] DEBUG ThreadPool.cpp:111 - thread pool init success 2022-05-10 11:09:44.943 [9924] DEBUG ThreadPool.cpp:128 - thread pool has been shutdown 

    错误结果为:

    ... Assertion `promise.use_count() == 1' failed. 2022-05-10 11:17:26.951 [10115] DEBUG ThreadPool.cpp:111 - thread pool init success 

    感谢大佬们看完我这丑陋的代码。。。

    22 条回复    2022-05-11 01:07:11 +08:00
    guang19
        1
    guang19  
    OP
       2022-05-10 11:23:51 +08:00
    补充下:环境是 ubuntu wsl gcc 11.2.0 ,C++17
    iOCZ
        2
    iOCZ  
       2022-05-10 11:35:27 +08:00
    哎,C++真难看懂,就跟和尚的头皮点了香似的
    guang19
        3
    guang19  
    OP
       2022-05-10 11:52:29 +08:00
    我这样写也有问题:
    ````
    auto asyncTask = ::std::make_shared<::std::packaged_task<RetType ()>>(
    ::std::bind(::std::forward<Fn>(func), ::std::forward<Args>(args)...));
    auto future = asyncTask->get_future();
    TaskType task = [asyncTask]() mutable
    {
    assert(asyncTask.use_count() == 1);
    (*asyncTask)();
    };
    assert(asyncTask.use_count() == 2);
    ````

    我发现这应该是 lambda 捕获 shared_ptr 引用错乱的问题,可能不是我代码的问题
    Inn0Vat10n
        4
    Inn0Vat10n  
       2022-05-10 12:14:53 +08:00
    inline 掉了吧,可以看看汇编代码
    guang19
        5
    guang19  
    OP
       2022-05-10 12:40:34 +08:00
    又 debug 了下,大概是我阻塞队列写的有点问题。。。
    statumer
        6
    statumer  
       2022-05-10 12:50:06 +08:00
    除非你已经定位到问题了,否则建议把代码贴全。你的 shared_ptr 被 task 捕获以后,task 如果被拷贝的话,shared_ptr 也会一块儿被复制。
    elfive
        7
    elfive  
       2022-05-10 12:58:36 +08:00 via iPhone
    我记得好像是 std::thread 有问题,资源不会及时释放,好像是会一直有一份拷贝存在。
    后来我只好用 boost::thread 替换了,才解决这个问题。
    guang19
        8
    guang19  
    OP
       2022-05-10 13:05:30 +08:00
    @statumer 谢谢老哥,我大概知道为什么 use_count 为 3 了,在 shared_ptr 传参的过程中被复制了几次,所以造成引用次数不一致。有时候任务线程执行的快,被拷贝的 shared_ptr 来不及析构,lambda 此时执行的时候的 use_count 就是 3 ;任务线程执行的慢,等其他被拷贝的被析构了,只剩下 lambda 捕获的 shared_ptr 了,此时执行 use_count 就是 1 。我又测试了好多遍,虽然 use_count 不一致,但 task 执行的结果却没错,这证明了 lambda 只会是最后一个 shared_ptr ,lambda 结束后并不会造成泄漏。
    guang19
        9
    guang19  
    OP
       2022-05-10 13:09:52 +08:00
    @elfive 因为我写的这个库是基于 linux 的,所以自己基于 posix pthread 封装的线程,cpp11 的 thread 太难用了,跟坨屎一样,创建之后非要调 join 或 detach 才会执行,join 阻塞主线程就不说了,而 detach 更是难用。
    codefun666
        10
    codefun666  
       2022-05-10 15:19:02 +08:00
    c++太复杂了,用 c 吧。

    看到```::```就头疼...
    hhhWhy
        11
    hhhWhy  
       2022-05-10 15:37:52 +08:00
    @guang19 没有看代码,但是这里有点不对,cpp11 的 thread 是在完成初始化之前运行的
    hhhWhy
        12
    hhhWhy  
       2022-05-10 15:39:21 +08:00
    ColorfulBoar
        13
    ColorfulBoar  
       2022-05-10 15:41:28 +08:00
    @guang19 你这也太离谱了……谁教你的 join/detach 之后才会执行?真心建议重新学习一下标准库里面的 thread

    constructor:
    Effects: The new thread of execution executes...
    Synchronization: The completion of the invocation of the constructor synchronizes with the beginning
    of the invocation of the copy of f.

    void join();
    Effects: Blocks until the thread represented by *this has completed.
    Synchronization: The completion of the thread represented by *this synchronizes with (6.9.2) the
    corresponding successful join() return...

    void detach();
    Effects: The thread represented by *this continues execution without the calling thread blocking.
    When detach() returns, *this no longer represents the possibly continuing thread of execution...
    guang19
        14
    guang19  
    OP
       2022-05-10 15:50:11 +08:00
    @ColorfulBoar 那请你帮个忙把这段代码贴到 linux 下去运行下,这个线程会不会执行,我的 archlinux 和 ubuntu 反正是不行的:
    ````
    ::std::thread t1([] ()
    {
    ::printf("%s\n", "hello world");
    });
    ::std::this_thread::sleep_for(::std::chrono::milliseconds(5000));
    ````
    guang19
        15
    guang19  
    OP
       2022-05-10 16:04:04 +08:00
    @ColorfulBoar 等 5 秒中都不执行的线程,非要手工指定 join 或 detach 状态的线程,你觉得离谱吗?
    ColorfulBoar
        16
    ColorfulBoar  
       2022-05-10 16:12:46 +08:00
    @guang19 这……你不会以为 printf 是直接写屏幕上的所以不显示等于没执行吧?你这个在当前 scope 结束的时候 t1 仍然是 joinable 的,所以 destructor 会调用 std::terminate(),然后 stdout 的缓冲区里面的东西就直接被扔了所以看起来什么都没有。你关了 buffer 或者手动刷新一下就能看出来了。
    guang19
        17
    guang19  
    OP
       2022-05-10 16:25:47 +08:00
    @ColorfulBoar 惊了,学习了,谢谢大佬。
    wzzzx
        18
    wzzzx  
       2022-05-10 21:17:19 +08:00
    @ColorfulBoar #16 我有个疑问想请教一下,我知道在当前 scope 结束后,t1 仍然是 joinable 的,但这是为什么丫?为什么要这么设计?明明已经执行完毕了,为什么还需要额外去调用一下 join 来保证它不是 joinable 的呢?
    wzzzx
        19
    wzzzx  
       2022-05-10 21:18:22 +08:00
    @ColorfulBoar #16 https://en.cppreference.com/w/cpp/thread/thread/joinable
    文档里也是这么说的
    ```
    A thread that has finished executing code, but has not yet been joined is still considered an active thread of execution and is therefore joinable.
    ```
    但是我不大理解这么设计的原因
    kilasuelika
        20
    kilasuelika  
       2022-05-10 21:48:07 +08:00 via Android   2
    你的 std 前为啥要用::?
    nightwitch
        21
    nightwitch  
       2022-05-11 00:01:21 +08:00   1
    在多线程环境下不要用 shared_ptr 的 use_count()以及 uniqiue() (已经在 C++20 被删除)这个 API 。
    cppreference 上明确标记了这两个 API 不保证线程安全,所以在多线程环境下其只返回一个可能的值,不是精确的。

    reference:https://en.cppreference.com/w/cpp/memory/shared_ptr/use_count

    @wzzzx 好问题,除了不会自动 join 以外,std::thread 也不能被打断或者取消,这个问题在 C++20 得到了修复。标准库加入了 std::jthread ,见 https://en.cppreference.com/w/cpp/thread/jthread
    ColorfulBoar
        22
    ColorfulBoar  
       2022-05-11 01:07:11 +08:00
    @wzzzx joinable()只是检测这个 std::thread object 是不是依然持有一个进程而已别的啥都没干,或许你想问的是为什么 std::thread 的 destructor 这么奇怪……当然不管想问的是啥,不管把标准还是实现重复一遍肯定都没意思,你一定想听个刺激的,所以让我恶意转述一下史官 Bjarne Stroustrup 的说法:我们当年也不想这样,但活在 [世界上只有 C 语言 + POSIX 的 P 意思是 portable] 这个梦里的 C 标准委员会因为 C 里面没有 RAII / POSIX 天生残疾就以为别人也不行,强烈反对我们干人事(这是唯一一份 C 标准委员会发给 C++标准委员会的正式通知),那就别怪我们摆烂了。
    关于     帮助文档     自助推广系统     博客     API     FAQ     Solana     2969 人在线   最高记录 6679       Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 26ms UTC 13:27 PVG 21:27 LAX 06:27 JFK 09:27
    Do have faith in what you're doing.
    ubao snddm index pchome yahoo rakuten mypaper meadowduck bidyahoo youbao zxmzxm asda bnvcg cvbfg dfscv mmhjk xxddc yybgb zznbn ccubao uaitu acv GXCV ET GDG YH FG BCVB FJFH CBRE CBC GDG ET54 WRWR RWER WREW WRWER RWER SDG EW SF DSFSF fbbs ubao fhd dfg ewr dg df ewwr ewwr et ruyut utut dfg fgd gdfgt etg dfgt dfgd ert4 gd fgg wr 235 wer3 we vsdf sdf gdf ert xcv sdf rwer hfd dfg cvb rwf afb dfh jgh bmn lgh rty gfds cxv xcv xcs vdas fdf fgd cv sdf tert sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf shasha9178 shasha9178 shasha9178 shasha9178 shasha9178 liflif2 liflif2 liflif2 liflif2 liflif2 liblib3 liblib3 liblib3 liblib3 liblib3 zhazha444 zhazha444 zhazha444 zhazha444 zhazha444 dende5 dende denden denden2 denden21 fenfen9 fenf619 fen619 fenfe9 fe619 sdf sdf sdf sdf sdf zhazh90 zhazh0 zhaa50 zha90 zh590 zho zhoz zhozh zhozho zhozho2 lislis lls95 lili95 lils5 liss9 sdf0ty987 sdft876 sdft9876 sdf09876 sd0t9876 sdf0ty98 sdf0976 sdf0ty986 sdf0ty96 sdf0t76 sdf0876 df0ty98 sf0t876 sd0ty76 sdy76 sdf76 sdf0t76 sdf0ty9 sdf0ty98 sdf0ty987 sdf0ty98 sdf6676 sdf876 sd876 sd876 sdf6 sdf6 sdf9876 sdf0t sdf06 sdf0ty9776 sdf0ty9776 sdf0ty76 sdf8876 sdf0t sd6 sdf06 s688876 sd688 sdf86