/* Page prose (zh-Hans) */
"WrappingUp" = "# 小结\n\n这两种工具回答的是两个不同的问题，而搞清楚你问的是哪一个，就已经赢了大半：组问的是“这一整批什么时候完成？”，信号量问的是“同时可以运行多少个？”两者都会奖励细心的记账，也会用挂起、崩溃和死锁来惩罚马虎。把下面这些平衡规则记牢。\n\n* **`DispatchGroup`** 跟踪一组任务，并在它们全部完成时触发；**`DispatchSemaphore`** 限制同时有多少个访问某个资源。\n* 两者可以**组合**：用信号量给同时运行的数量设上限，用组来得知整批何时完成，这正是 espresso 例子用到的那种搭配。\n* `notify(queue:)` 在组变空时运行一个回调，而且**从不阻塞**。优先用它。`wait()` 会阻塞调用它的线程，所以别把它放在主线程上。\n* **陷阱：** 不平衡的 `enter`/`leave` 要么**挂起**（leave 太少）要么**崩溃**（太多）；一个没有对应 `signal()` 的 `wait()` 会让排在它后面的所有人都死锁。紧跟其后的 `defer { group.leave() }` / `defer { sem.signal() }` 才是安全的写法。\n* **陷阱：** 把几十个被信号量阻塞的任务发到一个全局队列上，可能会耗尽 GCD 的线程池。对于繁重的节流，一个小型的专用队列（或者，稍后会讲的结构化并发）表现更好。\n* **陷阱：** 信号量限制的是同时运行*多少个*，而不是*哪一个*接下来运行：不要依赖任务获取许可的顺序。\n* 计数为 1 时信号量会充当锁，但存在更安全、更难忘记的工具，而 actor 将会彻底取代它。\n\n---\n\n## 接下来\n\n下一章是 [并发的陷阱](Concurrency%20Hazards.playgroundchapter/Overview)：你在计数例子里瞥见的那个 data race，加上死锁和优先级反转，当共享状态和阻塞相撞时会出什么错，以及如何对它进行推理。\n\n[上一页](@previous)  |  第 8 / 8 页";
