Nodejs 为什么用 co + map 的时候不会释放内存

发布于 1周前 作者 ionicwang 来自 nodejs/Nestjs

Nodejs 为什么用 co + map 的时候不会释放内存

snippet1

co(function* () {
  yield largeArray.map(function* (e) {
     const k = yield db.col.findById(e).exec()
     do something with k
  })
})

snippet2

Promise.map(largeArray, (e) => {
   return db.col.findById(e).exec()
   	.then(k => {
    	do something with k
    })
})

这上面用的库有

  • tj/co
  • mongoose

完成的操作就是遍历 id array ,然后把数据从 mongodb 从取出来操作下。但是经过测试,发现 snippet1 会导致内存溢出,但是 snippet2 不会。感觉好像 snippet1 在function* (){}生命周期结束之后,不会马上释放内存。没有研读过tj/co的源码,求大神指点


10 回复

把 snippet1 里的 generator 也换成 promise 试试:

co(function* () {
  yield largeArray.map(function (e) {
     return db.col.findById(e).exec()
        .then(k => {
            do something with k
        })
  })
})


PS: 话说你的 largeArray 究竟有多大呀?


你的 snippet1 真的 work 吗?你只给 yield largeArray.map 包了 co , largeArray.map 也支持 generator ?

应该是 co 支持 generator array

largeArray 也不会很大,就几万,但是中间查询的结果数据量比较大

原因是,在 snippet1 中, largeArray.map 并不会等所有 callback 执行完后才返回,也就是说 Array.prototype.map 并没有 『如果 callback 返回的是个 Promose 则等待』的功能,这种情况就应该用 Promise.map

确实,不过 https://www.npmjs.com/package/co#yieldables
array (parallel execution) 这应该是问题所在

如果 snippet2 Promise 用的是 bluebird ,面里的 Promise.map 也是并行执行的: http://bluebirdjs.com/docs/api/promise.map.html

#7 那就是 snippet1 的 do something with k return k 了, snippet2 没有

对 co 不太熟,我把它转成 async/await 的形式是这样没错吧?

await largeArray.map(async (e) => {
const k = await db.col.findById(e).exec()
do something with k
})

在Node.js中,使用co(一个基于生成器的异步流程控制库)与map(数组方法)时,内存未能释放的问题通常不是直接由这两个工具本身引起的,而是更可能与异步任务的处理、闭包、内存泄漏等问题相关。

首先,co用于将基于生成器的函数转换为返回Promise的函数,它本身并不会导致内存泄漏。然而,如果在生成器函数内部不正确地管理资源(如未关闭数据库连接、未取消HTTP请求等),则可能导致内存无法释放。

关于map,它是一个同步方法,用于遍历数组并返回一个新数组。如果map回调函数中创建了大型对象或未释放的资源,这些对象会留在内存中,直到垃圾回收器(GC)运行。

下面是一个简单的示例,展示了如何在comap中正确管理异步任务:

const co = require('co');

co(function*() {
  const results = yield Promise.all(
    [1, 2, 3].map(num => {
      return new Promise((resolve, reject) => {
        // 模拟异步操作,确保资源被正确释放
        setTimeout(() => {
          console.log(`Processed ${num}`);
          resolve(num * 2);
        }, 1000);
      });
    })
  );
  console.log(results);
}).catch(err => console.error(err));

在这个例子中,我们使用了Promise.all来处理异步任务,确保所有任务完成后才继续执行。同时,由于setTimeout不会创建持久的资源,因此不存在内存泄漏的风险。如果在实际应用中遇到内存问题,建议检查异步任务的处理和资源释放情况。

回到顶部