V2EX = way to explore
V2EX 是一个关于分享和探索的地方
现在注册
已注册用户请  登录
iceiceshen24
V2EX  ›  Node.js

求解一个 promise 内存泄漏问题

  •  
  •   iceiceshen24 · 2016-06-22 17:10:03 +08:00 · 6115 次点击
    这是一个创建于 3081 天前的主题,其中的信息可能已经有所发展或是发生改变。

    被一个关于 promise 内存泄漏的问题所困扰,希望大家可以帮忙解惑~

    A 内存泄漏

    'use strict';
    
    var i = 0;
    function run() {
        return new Promise(function(resolve) {
            i++;
            setTimeout(function() {
                if (i === 10000 * 10) return resolve();
                resolve(run());
            }, 0);
        })
    .then(function() {});
    }
    run();
    

    B 内存不泄露

    与 A 相比即将 function run()中的第一个 return 去掉。

    'use strict';
    
    var i = 0;
    function run() {
        new Promise(function(resolve) {
            i++;
            setTimeout(function() {
                if (i === 10000 * 10) return resolve();
                resolve(run());
            }, 0);
        })
    .then(function() {});
    }
    run();
    

    C 内存不泄露

    与 A 相比将中的.then(function() {})去掉。

    'use strict';
    
    var i = 0;
    function run() {
        return new Promise(function(resolve) {
            i++;
            setTimeout(function() {
                if (i === 10000 * 10) return resolve();
                resolve(run());
            }, 0);
        });
    }
    run();
    
    15 条回复    2016-11-28 21:37:06 +08:00
    bearice
        1
    bearice  
       2016-06-22 17:49:10 +08:00
    C 并不是不泄露,只是泄露的少吧

    https://gist.github.com/bearice/ac52f65e4a1b22c7074e0a2e2d818967
    kenshinhu
        2
    kenshinhu  
       2016-06-22 17:52:50 +08:00
    @bearice 弱弱问问这个是怎样测试的
    bearice
        3
    bearice  
       2016-06-22 17:58:38 +08:00
    @kenshinhu node --log_gc --trace_gc test.js
    xuzicn
        4
    xuzicn  
       2016-06-22 19:02:06 +08:00
    并没有泄露啊
    第一个代码不停的在压栈,不停的在构建执行上下文而且不释放。
    第二个代码释放得很干净
    第三个压栈的速度只有第一个压栈的一半。
    mcfog
        5
    mcfog  
       2016-06-22 19:18:21 +08:00
    用很多内存和内存泄露是两回事情啊

    内存泄露是“该回收没回收”,这段代码只是构造了“很长的调用栈,大量信息不该回收”的场景而已,算不上内存泄露。
    Sparetire
        6
    Sparetire  
       2016-06-22 23:24:12 +08:00
    我也觉得应该是用了很多内存而不是泄露,初学 Promise ,回忆一下死去的操作系统和编译原理知识。。不知道对不对哈。
    记得 Promise A+规范是说 resolve 的参数如果是一个 Promise 会等这个状态完成。
    对于 AC, 每一个 run 都要等待下一个 run 的 Promise 的状态及它的 then 的状态确定下来,应该是一直不停压栈直到最后一个 run 执行完才退栈,而 B 的 run 没有 return ,等于是 resolve(undefined),每一个 run 执行完,里面的 Promise 的状态就确定了,执行完一个 run 就退一个栈而不用等到最后一个 run 执行完才退栈。
    而 A 和 C, 虽然都要等最后一个 run 执行完,但 C 每个 run 只要等一个 Promise 状态确定,而 A 要等两个,开销应该也小很多。
    iceiceshen24
        7
    iceiceshen24  
    OP
       2016-06-23 10:13:30 +08:00
    @bearice 你这个指令好厉害,受教了~但是我试了一遍你这个指令,发现 C 占用内存是一直没有变得呀,弱弱地说一哈之前一直用 top 看内存泄漏
    iceiceshen24
        9
    iceiceshen24  
    OP
       2016-06-23 10:40:43 +08:00
    @breeswish 这个我感觉他没讲清楚
    xuzicn
        10
    xuzicn  
       2016-06-23 13:59:21 +08:00
    @breeswish 他这个文章有错。一会我补一篇来讲明问题
    @Sparetire 你的前半段理解我认同,后面的也有不准确的地方
    iceiceshen24
        11
    iceiceshen24  
    OP
       2016-06-23 15:34:46 +08:00
    @xuzicn nice !
    xuzicn
        12
    xuzicn  
       2016-06-24 11:21:08 +08:00   ❤️ 1
    @iceiceshen24
    @breeswish
    @Sparetire

    懒得写文了,只需要在他的 A 段代码后,强制 gc 一次就可以看到区别。运行 node --expose-gc test.js ,最后一次 print 的内存比第二个 print 还少。


    (function() {
    function printMemory() {
    console.log(process.memoryUsage())
    }

    // 记录 Promise 链的长度
    var i = 0;
    function run() {
    return new Promise(function(resolve) {
    // 每增加 10000 个 Promise 打印一次内存使用情况
    if (i % 100 === 0) printMemory();
    i++;
    // 模拟一个异步操作
    setTimeout(function() {
    // 1000 个 Promise 之后退出
    if(i === 100 * 10) return resolve();
    // 如果 resolve 的参数是一个 Promise ,外层 Promise 将接管这个 Promise 的状态,构成嵌套 Promise
    resolve(run());
    }, 0);
    }).then(function() {
    // console.log(j);
    return true;
    });
    }
    run().then(function (r) {
    global.gc()
    console.log(111)
    printMemory();
    });
    })();
    Sparetire
        13
    Sparetire  
       2016-06-25 00:52:14 +08:00
    @xuzicn 3Q, 明天试试
    iceiceshen24
        14
    iceiceshen24  
    OP
       2016-06-30 10:46:33 +08:00
    @xuzicn 3Q~~
    poke707
        15
    poke707  
       2016-11-28 21:37:06 +08:00
    看 co 的一些代码发现 https://github.com/tj/co/issues/180

    在一些内部是死循环的生成器中,相当于楼主的例子,是可以永远不 resolve / reject 的。那么 A 中串联的所有 promise 都得不到释放了, B 只有外层一个 promise 没有释放。
    关于   ·   帮助文档   ·   博客   ·   API   ·   FAQ   ·   实用小工具   ·   2775 人在线   最高记录 6679   ·     Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 · 33ms · UTC 00:03 · PVG 08:03 · LAX 16:03 · JFK 19:03
    Developed with CodeLauncher
    ♥ Do have faith in what you're doing.