我图省事调了个异步函数发通知,既没 await 也没 catch,想着失败了无所谓,结果它一旦 reject 整个 Node 进程就因为未处理的 Promise 拒绝直接崩溃退出:一次 unhandled rejection 拖垮服务的深度复盘

我有个发通知的异步函数 sendNotification(user),心想通知失败也无所谓、不影响主流程,就图省事写成 fire-and-forget:sendNotification(user)——既没 await 也没 .catch(),调完不管。平时风平浪静,直到某次通知下游抽风、它内部的 Promise reject 了,而我没有任何地方处理这个 reject,整个 Node 进程因为 UnhandledPromiseRejection 直接崩溃退出、把主服务也带挂了。复盘才理解:Promise 的 reject 必须被某处处理(.catch 或 async/await 里 try-catch),没人接就是未处理的拒绝;而 Node v15 起对未处理拒绝默认让进程崩溃退出(fail-fast,因为未处理错误意味着程序处于未知状态);我那行 fire-and-forget 把返回的 Promise 完全丢弃、没接住可能的 reject,平时 resolve 没事、一旦 reject 就掀翻进程。根因是我以为不关心结果就能不管,连失败也没接。这篇复盘从故障现场讲到 reject 必须被处理、未处理拒绝的后果、会漏接 reject 的常见写法,再到 await 配 try-catch、fire-and-forget 也要 .catch、并发用 allSettled、全局 unhandledRejection 兜底的完整正解,以及其他异步错误处理的坑,和不关心成功不等于可以不管失败、错误不会自己消失没人接住就被粗暴默认接管、发起一件事要为它的所有结局负责的认知。

我图省事调了个异步函数发通知,既没 await 也没 catch,想着失败了无所谓,结果它一旦 reject 整个 Node 进程就因为未处理的 Promise 拒绝直接崩溃退出:一次 unhandled rejection 拖垮服务、误以为不关心结果就能不管错误的深度复盘

那次主服务"毫无征兆地整个挂掉",查了好久才发现祸根是一行我以为"无关紧要"的异步调用。我有个发通知的异步函数 sendNotification(user),在某个流程里要调它。我心想"通知嘛,发失败了也无所谓,不影响主流程",于是图省事写成了 fire-and-forget:sendNotification(user);——既没 await,也没 .catch(),调完就不管了。平时风平浪静。直到某次,通知服务的下游抽风,sendNotification 内部的 Promise reject 了,而我没有任何地方处理这个 reject。结果:整个 Node 进程因为 UnhandledPromiseRejection 直接崩溃退出了,把好端端的主服务也一起带挂了——一个"无所谓的通知",搞死了整个进程。复盘这件事,我才真正理解了 Promise 拒绝处理的机制,以及我犯的认知错误:问题出在我以为"我不关心这个异步操作的结果,就可以完全不管它"。一个 Promise 有两种结局:resolve(成功)和 reject(失败);reject 必须被某个地方"处理"(用 .catch(),或在 async/await 里用 try-catch 接住);如果一个 Promise reject 了、却没有任何 catch 处理它,就会产生一个"未处理的拒绝(unhandled rejection)";Node.js 对未处理拒绝的默认行为,从 v15 起变成了"直接让进程崩溃退出(以非零码)"(在那之前是警告)——因为未处理的错误意味着程序处于未知状态,继续运行更危险;我那行 fire-and-forget 的 sendNotification(user),把它返回的 Promise 完全丢弃了、没接住可能的 reject——平时它 resolve 没事,一旦 reject,这个"无人处理的拒绝"就掀翻了整个进程。根本原因是:Promise 的 reject 必须被处理(.catch 或 try-catch),未处理的拒绝会产生 unhandledRejection、在新版 Node 下直接让进程崩溃;我以为"不关心结果就能不管",fire-and-forget 时连失败也没接,导致一次 reject 拖垮整个服务。问题的根,是未处理的 Promise 拒绝——我 fire-and-forget 调用异步函数时既没 await 也没 catch,误以为不关心结果就能不管错误,而未处理的 reject 在新版 Node 下会直接崩溃进程。这篇就把这次"unhandled rejection"的坑,从头到尾复盘一遍。

故障现场:一个没人管的 reject,崩了整个进程

问题在于 fire-and-forget 的异步调用没有处理可能的 reject:

// 我图省事的写法(以为"通知失败无所谓", 就不管了):
function handleOrder(order) {
  // ... 处理订单的主流程 ...
  sendNotification(order.user);   // ✗ 没await、没.catch() —— 返回的Promise被丢弃!
  // 我的想法: "通知嘛, 失败了也不影响订单, 不用管"
  return { ok: true };
}

async function sendNotification(user) {
  const resp = await fetch(notifyUrl, {...});   // 某次下游抽风, 这里reject(网络错误/超时)
  if (!resp.ok) throw new Error("通知发送失败");  // 或这里抛错 → Promise reject
}

/*
灾难是怎么发生的:
  1. handleOrder 调 sendNotification(user), 不await不catch → 拿到一个Promise, 直接扔掉;
  2. 平时 sendNotification 内部 resolve → 没事(那个被扔掉的Promise悄悄成功);
  3. 某次下游抽风, sendNotification 内部 reject(fetch失败/throw);
  4. 这个reject 没有任何 .catch() 或 try-catch 接住 → 成为"未处理的拒绝(unhandled rejection)";
  5. Node.js (v15+) 对未处理拒绝的默认行为 = 打印错误 + 进程以非零码退出(崩溃)!
  6. 于是: 一个"无所谓的通知失败" → 掀翻了整个Node进程 → 主服务一起挂掉。

为什么 Node 要这么"狠"(崩溃而非忽略):
  - 一个未被处理的错误, 意味着"有异常情况发生了, 但没有任何代码知道、没人处理它";
  - 程序此时处于"未知、可能不一致"的状态, 继续跑下去可能造成更隐蔽的破坏;
  - 所以 Node 选择 fail-fast: 与其带病运行, 不如崩溃(让你立刻知道有未处理的错误)。
  - (v15前是 UnhandledPromiseRejectionWarning 警告, 之后默认变为 crash, 越来越严格)

★ 我的认知错误: "我不关心这个异步操作成不成功, 就可以完全不管它(连失败也不管)。"
  真相: 不关心"成功的结果", 不等于可以不管"失败的错误";
        reject 是个必须被接住的东西——没人接, 它就成了unhandled rejection, 掀翻进程。
        fire-and-forget 也必须 .catch(), 哪怕只是记个日志。
*/

看着监控里主进程"无故重启"的记录,顺着退出日志找到那行 UnhandledPromiseRejection,我又懊恼又后怕:"我以为'通知失败无所谓'就能甩手不管,谁知道那个没人接的 reject 能把整个进程掀翻……我不关心它成功,但我'不管它失败'这件事本身,才是真正的祸根。"这个坑最隐蔽的地方在于:平时完全正常(Promise 都 resolve 时,fire-and-forget 毫无问题),给你"这样写没事"的错觉;只有在那个被你忽略的 reject 真的发生时才爆发,而且爆发形式极其严重(整个进程崩溃),还和那行不起眼的代码看起来八竿子打不着,极难定位下面就来拆解,异步操作的错误到底该怎么管。

第一件事:搞懂 Promise 拒绝必须被处理

我顺着这次事故,把 Promise 的错误处理机制彻底理清了。

为什么未处理的 Promise 拒绝这么危险? 该怎么处理?

【核心: Promise的reject必须被.catch或try-catch处理; 未处理的拒绝=unhandledRejection, 新版Node默认
   崩溃进程; "不关心成功结果"不等于"可以不管失败"; fire-and-forget也必须.catch, 哪怕只记日志】

1. Promise 的两种结局, reject 必须有人接:
   - resolve(成功) / reject(失败); 处理成功用 .then 或 await 的返回值;
   - 处理失败用 .catch(), 或在 async/await 里用 try-catch 包住 await;
   - reject 若没有任何 catch 接住 → "未处理的拒绝(unhandled rejection)"。

2. 未处理拒绝的后果(随环境而异, 都不好):
   - Node.js v15+: 默认让进程崩溃退出(fail-fast); v15前是警告;
   - 浏览器: 触发 unhandledrejection 事件, 控制台报错(一般不崩页面但错误被忽略);
   - 无论哪种: 错误被"无声地丢失"或"以严重方式爆发", 都是隐患。

3. 几种会漏掉 reject 的常见写法:
   - fire-and-forget: doAsync();  // 不await不catch, 返回的Promise被丢弃(本次);
   - 忘了await: 在async函数里调另一个async却没await, 错误不会进当前try-catch;
   - .then没跟.catch: p.then(onOk);  // 只处理成功, reject没人管;
   - forEach里用async(同351篇): 回调返回的Promise被forEach丢弃, reject漏掉;
   - await一组Promise但没整体catch。

4. 正确处理(每个异步操作的失败都要有归宿):
   ① await 的, 用 try-catch 包: try { await doAsync(); } catch(e) { handle(e); }
   ② fire-and-forget 的, 也要 .catch: doAsync().catch(e => log.error(e));  // 哪怕只记日志
   ③ .then 要跟 .catch: p.then(onOk).catch(onErr);
   ④ 并发的用 Promise.allSettled(看每个成败) 或 Promise.all(整体try-catch);
   ⑤ 设全局兜底: process.on('unhandledRejection', ...) 记录日志/告警(兜底, 不是借口)。

5. 一个关键区分: "不关心结果" ≠ "不处理错误"
   - 你可以不关心"通知发成功了没"(不需要它的返回值);
   - 但你必须"处理它万一失败的情况"(至少catch住记个日志, 别让它成为unhandled);
   - 否则: 不是"无所谓", 而是"埋了颗会掀翻进程的雷"。

一句话: Promise的reject必须被.catch或try-catch接住; 未处理拒绝在新版Node会崩溃进程;
   "不关心成功结果"不等于"可以不管失败的错误", fire-and-forget也必须.catch住(哪怕只记日志)。

这套认知,是整个坑的根。reject 必须有人接:处理失败用 .catch() 或 async/await 里的 try-catch;没人接就是 unhandled rejection未处理拒绝的后果:Node v15+ 默认崩溃进程(fail-fast)、浏览器触发事件、错误被无声丢失——都是隐患会漏掉 reject 的写法:fire-and-forget、忘了 await、.then 没跟 .catch、forEach 里用 async、并发没整体 catch正确处理:await 的用 try-catch、fire-and-forget 也要 .catch、.then 跟 .catch、并发用 allSettled/整体 catch、设全局 unhandledRejection 兜底关键区分:"不关心结果"≠"不处理错误"——可以不要返回值,但必须处理万一失败(至少 catch 记日志)一句话:Promise 的 reject 必须被 .catch 或 try-catch 接住;未处理拒绝在新版 Node 会崩溃进程;"不关心成功结果"不等于"可以不管失败的错误",fire-and-forget 也必须 .catch 住(哪怕只记日志)。

第二件事:正解——每个异步操作的失败都要有归宿

知道了 reject 必须被处理,正解就清楚了:给每个异步操作的失败都安排一个"接住的地方"。

// 正解1: await 的, 用 try-catch 包住
async function handleOrder(order) {
  try {
    await sendNotification(order.user);
  } catch (e) {
    log.error("通知发送失败, 但不影响订单", e);   // 处理失败(这里只记日志, 不中断主流程)
  }
  return { ok: true };
}

// 正解2: fire-and-forget 也必须 .catch(本次该做的)
function handleOrderV2(order) {
  // 确实不想等通知, 但必须接住它可能的失败:
  sendNotification(order.user).catch(e => log.error("通知失败", e));  // ✓ 不再是unhandled
  return { ok: true };
}

// 正解3: .then 要跟 .catch
fetchData()
  .then(data => render(data))
  .catch(err => showError(err));   // 别只写.then不写.catch

// 正解4: 并发——用 allSettled 看每个成败, 或 all + 整体catch
const results = await Promise.allSettled([taskA(), taskB(), taskC()]);
results.forEach(r => {
  if (r.status === "rejected") log.error("子任务失败", r.reason);   // 每个失败都看到
});
// 或: try { await Promise.all([...]); } catch(e) {...}  // 任一失败就进catch(但其他可能还在跑)

// 正解5: 全局兜底(最后一道防线, 不是不写catch的借口)
process.on("unhandledRejection", (reason, promise) => {
  log.fatal("出现未处理的Promise拒绝!", reason);   // 记录+告警, 别让它静默崩溃
  // 视情况: 优雅关闭、上报监控; 但根治还是靠每处都正确catch
});

// 反例(别这样):
// sendNotification(user);              // 裸调用, reject没人管 → 可能崩进程
// arr.forEach(async x => await f(x));  // forEach丢弃了async返回的Promise, reject漏掉(同351篇)

// 核心: 每个异步操作的失败都要有归宿——await配try-catch、fire-and-forget配.catch、
//   .then配.catch、并发用allSettled; 全局unhandledRejection做兜底, 但不能替代逐处处理。

这套正解的关键,是让每一个异步操作的"失败"都有一个明确"接住它的地方",不留任何裸奔的 Promiseawait 的用 try-catch:把可能 reject 的 await 包起来,失败时按需处理(本次我可以只记日志、不中断主流程)。fire-and-forget 也要 .catch:确实不想等结果,也必须 .catch() 接住失败,哪怕只记日志——这正是本次我该做的。.then 跟 .catch、并发用 allSettled:别只处理成功路径。全局 unhandledRejection 兜底:作为最后一道防线记录告警,但绝不是省略逐处 catch 的借口。

第三件事:其他几个"异步错误处理"的坑

顺着这次 unhandled rejection,我把异步错误处理相关的几个坑也一并理了:

几个异步错误处理的坑:

坑1: 忘了 await, 错误逃出 try-catch:
   try { doAsync(); } catch(e) {...}   // ✗ 没await! doAsync的reject不会进这个catch(它已经返回了)
   正解: try { await doAsync(); } catch(e) {...}

坑2: forEach/map 里用 async(同351篇):
   list.forEach(async x => { await save(x); });  // forEach丢弃返回的Promise, 错误漏掉、也不等待
   正解: for...of + await 顺序; 或 await Promise.all(list.map(async x => save(x)))。

坑3: 在回调(非async上下文)里 throw:
   setTimeout(() => { throw new Error(); });  // 这个错误不在任何Promise/try-catch里, 直接崩
   正解: 回调内自己 try-catch; 或改用Promise化的API。

坑4: async 函数里同步抛错 vs 异步reject 混淆:
   async函数无论同步throw还是await的reject, 都体现为返回的Promise reject → 都要catch。

坑5: 吞掉错误(同578篇裸except)——catch(e){} 空捕获, 错误被静默吃掉, 问题更难查;
   正解: catch里至少记日志/上报, 别空着。

坑6: 只在最外层catch, 丢失上下文——一个大try包一切, 出错只知道"某处错了";
   正解: 在合适的粒度catch, 保留"哪个操作失败"的上下文。

共同的根: 异步代码里, 错误不会"自动沿调用栈冒泡到你写的try-catch"(因为调用栈已经返回了);
   它通过Promise的reject传播, 必须用.catch/await+try-catch显式接住; 每个异步操作的失败都要有归宿,
   "发起了一个异步操作"就意味着"要为它的失败负责到底"。

这些坑看似不同,根却是同一个:异步代码里,错误不会自动沿调用栈冒泡到你的 try-catch(因为发起异步操作的那个调用栈早就返回了);它通过 Promise 的 reject 传播,必须用 .catchawait + try-catch 显式接住认清这个根("异步错误要显式接、每个异步操作的失败都要有归宿"),才不会埋下"没人管的 reject"这种雷。

第四件事:异步错误的处理方式——两张对照表

我把几种异步调用方式、以及它们的错误该怎么接,整理成对照表,贴在了团队的 JS/Node 规范里:

调用方式 reject 谁来接 漏接的后果
await doAsync() 外层 try-catch 错误抛出,可能向上崩
doAsync()(不 await) 必须 .catch() unhandled,Node 崩进程
p.then(onOk) 需跟 .catch(onErr) reject 无人管
Promise.all([...]) 整体 try-catch 任一失败抛出
Promise.allSettled([...]) 遍历看 rejected —(不会抛,需自己查)
forEach(async ...) (无法接,别这么写) reject 全漏掉
需求 推荐写法 说明
要结果且要处理失败 try { await } catch 最常用
不要结果但不能崩 doAsync().catch(log) fire-and-forget 必加 catch
并发且要每个成败 Promise.allSettled 不会因一个失败丢全部
并发且一败即停 Promise.all + try-catch 整体失败处理
顺序异步遍历 for...of + await 别用 forEach(async)
全局最后防线 process.on unhandledRejection 记录告警,非省 catch 借口

这两张表的核心,第一张是每种异步调用方式都有"谁来接 reject"的明确答案——fire-and-forget 必须 .catch,forEach(async) 根本接不住别写;第二张是按需求选对写法,但无论哪种,失败都必须有归宿。记住一条:写下一个异步调用时,顺手问"这个 Promise 万一 reject,谁接?"——答不上来,就是个雷。

第五件事:关于异步错误的几组容易想当然的认知

这次事故也让我厘清了几组关于异步错误处理的、容易想当然的概念:

直觉以为 实际上
不关心结果就可以不管这个异步调用 不关心成功≠可以不管失败,reject 要接
fire-and-forget 不加 catch 没事 reject 时成 unhandled,新版 Node 崩进程
通知失败无所谓,不影响主流程 没接的 reject 能掀翻整个进程
try-catch 能接住里面所有错误 没 await 的异步 reject 逃出 try-catch
forEach 里 await 能顺序等待 forEach 丢弃 Promise,不等待也不接错误
设了全局 unhandledRejection 就够了 那是兜底,不能替代逐处正确处理
异步错误会冒泡到调用处 调用栈已返回,错误经 reject 传播需显式接

这张表里,我栽的是第一行和第二行:把"不关心通知成不成功"理解成了"可以完全不管这个调用",连它失败也不接,以为顶多是通知没发出去,没想到没人接的 reject 直接掀翻了进程厘清这些,核心是一个意识:发起一个异步操作,就意味着要为它的所有结局(尤其是失败)负责;"不需要它的成功结果"和"不处理它的失败"是两码事——后者是埋雷。

第六件事:写异步调用时,我现在的自检习惯

现在每当我写下一个异步调用,我都会先按这张图问自己:

这张图的精髓,是"每个异步调用都问一句'它reject了谁接'、fire-and-forget 也要 catch、别忘 await"核心一问:它 reject 了谁来接?要结果就try-catch、不要结果也.catch 记日志、链式跟 .catch、并发allSettled、别忘await这套习惯,让我从"不关心结果就不管"变成了"每个异步操作的失败都有归宿"——核心始终是:Promise 的 reject 必须被 .catch 或 try-catch 接住;未处理拒绝在新版 Node 会崩溃进程;不关心成功结果不等于可以不管失败,fire-and-forget 也必须 .catch。

我立下的几条规矩

这场"没人管的 reject 崩了整个进程"的事故,换来了我写异步代码时,刻进骨子里的几条铁律:

  1. Promise 的 reject 必须被处理(.catch 或 async/await 的 try-catch),否则是 unhandled rejection。
  2. 未处理的拒绝在 Node v15+ 默认让进程崩溃退出,危害极大。
  3. "不关心成功结果" ≠ "可以不管失败的错误";fire-and-forget 也必须 .catch,哪怕只记日志。
  4. await 的异步调用要用 try-catch 包;没 await 的 reject 不会进 try-catch。
  5. .then 要跟 .catch;并发用 Promise.allSettled 看每个成败,或 all + 整体 try-catch。
  6. 别在 forEach 里用 async(丢弃 Promise、漏掉错误);用 for...of + await 或 Promise.all(map)。
  7. 设 process.on('unhandledRejection') 做全局兜底记录,但它不能替代逐处正确处理。

附:一个 fire-and-forget 的安全封装

借这次的坑,我写了个小工具,把"发起一个不等待结果的异步操作、但保证它的失败有归宿"封装起来,杜绝裸奔的 Promise。

// 安全的 fire-and-forget: 不等待结果, 但保证失败被记录, 不会成为 unhandled rejection
function fireAndForget(promiseFactory, label = "background task") {
  Promise.resolve()
    .then(promiseFactory)                       // 执行异步操作
    .catch(err => {
      // 失败有归宿: 记录日志 + 上报监控, 而非裸奔崩进程
      log.error(`[${label}] 后台任务失败`, err);
      metrics.increment("background_task_failed", { label });
    });
}

// 用法: 想 fire-and-forget 时, 用它包一下, 失败自动被接住
function handleOrder(order) {
  // ... 主流程 ...
  fireAndForget(() => sendNotification(order.user), "发送订单通知");  // ✓ 失败只记日志, 不崩
  return { ok: true };
}

// 配合全局兜底(双保险):
process.on("unhandledRejection", (reason) => {
  log.fatal("仍有未处理的Promise拒绝, 说明有地方漏了catch!", reason);
  // 记录下来, 作为"还有漏网之鱼"的告警信号
});

// 原则: 把"fire-and-forget 也要接住失败"这件容易忘的事, 封装成一个一眼就对的函数;
//   让"安全地不等待"成为顺手的默认, 而不是依赖每个人都记得手写 .catch。

这个封装很小,但它把"容易忘、忘了就埋雷"的事做成了"顺手就对":想 fire-and-forget 时,用 fireAndForget(...) 包一下,失败自动被记录、绝不会成为 unhandled rejection;再配合全局 unhandledRejection 兜底作为"还有漏网之鱼"的告警——双保险。让"安全地不等待"成为默认,比反复提醒"记得加 catch"可靠得多

写在最后

回头看,这场由"一个没人处理的 Promise 拒绝"引发的整个进程崩溃,真正教给我的,远不止"fire-and-forget 也要 catch"这一个技巧。它让我对"'不在乎一件事的好结果' 和 '可以不管这件事的坏结果', 是两回事; 我常常因为'我不需要它成功'就以为'我可以彻底撒手不管', 却忘了——我发起的这件事, 它的'失败'本身也是一个需要有人负责的结果",有了一次刻骨的体会。我栽跟头,是因为我把"我不关心这个通知发没发成功"偷换成了"我可以对这个操作完全不闻不问"——我以为"不要它的成功"就等于"不必管它的一切";可我漏掉了一个关键: "失败"不会因为我不关心它就消失; 一个被发起的操作, 一旦失败, 这个失败总要有个去处——我不接住它, 它就会沿着系统的默认规则, 以我意想不到、也无法控制的方式爆发(在这里, 就是掀翻整个进程);我以为的"撒手不管, 顶多通知没发出去", 实际是"我放弃了对一个错误的处置权, 把它交给了最粗暴的默认处理(崩溃)"这让我领悟到一个关于"责任、结果与善后"的深刻认知:当你发起一件事(一个异步操作、一个请求、一个承诺、一项委托)时,你就对它的所有可能结局(成功和失败)负有了责任——"我不需要它的好结果" 绝不等于 "我可以无视它的坏结果";"失败/错误/烂摊子" 是一种不会自己消失的东西: 你不主动接住、妥善处置, 它就会被默认规则接管, 而默认规则往往是最粗暴、代价最大的那种(崩溃、数据不一致、责任不清、影响扩散);真正的负责, 是"有头有尾"——发起了, 就要为它的每一种结局都安排好"谁来接、怎么处置", 而不是只享受顺利时的省事、却把出问题时的烂摊子留给系统/别人/未来去粗暴收场这给了我一种做任何"发起型动作"时的自觉:每当我"发起一件事就想转身离开"时,要追问"这件事如果失败了, 由谁、以什么方式来收场?这个收场方式我能接受吗?"——哪怕我不在乎它成功, 也要为它的失败安排一个明确、可控的归宿(哪怕只是记一笔日志), 而不是让没人处理的错误去触发系统最粗暴的默认行为;"对自己发起的每件事的失败结局负责到底、为错误安排好归宿",是区分'真正可靠'与'只在顺境里省事'的关键认清不关心成功不等于可以不管失败、错误不会自己消失没人接住就被粗暴默认接管、发起一件事要为它的所有结局负责——这,是我用一次 unhandled rejection 崩溃的事故,换来的、关于 JavaScript 异步、也关于如何对自己发起之事负责的、最朴素也最深刻的领悟。如果这篇复盘,能让你下次写下一个 fire-and-forget 的异步调用时,顺手补上一个 .catch(e => log.error(e)),那我对着那次主进程"无故崩溃"排查到深夜的这段时间,就值了。

—— 别看了 · 2026
声明:本站所有文章,如无特殊说明或标注,均为本站原创发布。任何个人或组织,在未征得本站同意时,禁止复制、盗用、采集、发布本站内容到任何网站、书籍等各类媒体平台。如若本站内容侵犯了原著者的合法权益,可联系我们进行处理 邮箱1846861578@qq.com。
技术教程

我在 Python 里一边遍历字典一边删掉满足条件的键,本以为天经地义,结果程序直接抛 RuntimeError 说字典在迭代时改变了大小:一次遍历时修改容器、误以为可以边用边改的深度复盘

2026-6-3 0:31:15

技术教程

我用 WaitGroup 等一批 goroutine 全部干完,却把 wg.Add 写进了 goroutine 内部,结果主协程的 Wait 在 Add 之前就跑完直接返回,程序以为活儿都干完了其实大半还没开始:一次 WaitGroup 时序用错的深度复盘

2026-6-3 0:42:40

0 条回复 A文章作者 M管理员
    暂无讨论,说说你的看法吧
个人中心
购物车
优惠劵
今日签到
有新私信 私信列表
搜索