# 同步与线程注解 · Mutex、SequenceChecker、RTC_GUARDED_BY

> 读 WebRTC 源码满眼 RTC_GUARDED_BY、RTC_RUN_ON——这些不是注释，是编译器能检查的线程安全注解。这一章讲 Mutex/Event/SequenceChecker 这些同步原语，以及 WebRTC 那套「用线程归属代替锁、让编译器帮你抓竞争」的独特哲学。

- 作者：David（道雾轩）
- 专栏：音视频与 WebRTC 详解（https://daiw.org/manual/webrtc.md）
- 最后更新：2026-07-14
- 原文：https://daiw.org/manual/webrtc/synchronization
- 转载与引用：请注明出处并附原文链接（https://daiw.org/about/copyright）

# 同步与线程注解 · Mutex、SequenceChecker、RTC_GUARDED_BY

一路读源码，你一定见过密密麻麻的 `RTC_GUARDED_BY(worker_thread())`、`RTC_RUN_ON(network_thread())`。它们看着像注释，其实是**编译器能检查的线程安全约束**。这一章讲 WebRTC 的同步原语，以及它那套颇有特色的并发纪律。

## 先给同步原语

`rtc_base` 提供跨平台的同步原语（呼应它[吃掉平台差异的使命](https://daiw.org/manual/webrtc/rtc-base-overview)）：

| 原语 | 源码 | 作用 |
| --- | --- | --- |
| `Mutex` | `synchronization/mutex.h` | 互斥锁（平台后端：abseil / pthread / Win 临界区） |
| `Event` | `event.h` | 事件：一个线程等，另一个线程 set 唤醒 |
| `SequenceChecker` | `synchronization/sequence_checker_internal.h` | 运行期检查“这段代码是否在预期线程 / 序列上跑” |

`Mutex` 按平台有不同后端（`mutex_abseil.h` / `mutex_pthread.h` / `mutex_critical_section.h`），上层只用 `Mutex` 一个名字——又一次[平台差异被封在底下](https://daiw.org/manual/webrtc/thread-and-taskqueue)。但真正体现 WebRTC 特色的，不是锁本身，而是**它尽量不用锁**。

## 核心哲学：用线程归属代替锁

<Callout type="info">
  **WebRTC 的并发哲学是“**能不加锁就不加锁**”——不是靠 `Mutex` 保护共享数据，而是靠“**规定每份数据只属于某一条线程**”，跨线程访问一律[通过 `PostTask` 投递到那条线程](https://daiw.org/manual/webrtc/thread-and-taskqueue)，从而根本不产生竞争。** 回想[上一章说的](https://daiw.org/manual/webrtc/thread-and-taskqueue)：[`Thread` 是任务队列、任务串行执行](https://daiw.org/manual/webrtc/thread-and-taskqueue)。于是：

  - **一份数据“归属”某线程** —— 比如 [`Call` 的媒体状态归 worker 线程](https://daiw.org/manual/webrtc/call-object)、[网络状态归 network 线程](https://daiw.org/manual/webrtc/threading-model)。
  - **只在归属线程访问它** —— 因为该线程的任务[串行跑](https://daiw.org/manual/webrtc/thread-and-taskqueue)，同一时刻只有一个任务在碰这份数据，**没有并发，自然不需要锁**。
  - **别的线程想碰？投递过去** —— [`PostTask` 到归属线程](https://daiw.org/manual/webrtc/thread-and-taskqueue)，让访问在正确的线程上排队进行。

  这比“共享 + 加锁”**更快也更不易错**：没有锁竞争、没有死锁、没有忘记加锁的 bug。锁只在少数“[数据确实需要被多线程直接共享](https://daiw.org/manual/webrtc/rtp-transport-controller-send)”的地方才用。这就是为什么 WebRTC 是[三线程 / 多任务队列](https://daiw.org/manual/webrtc/threading-model)的架构——**架构本身就是它的并发安全策略**。
</Callout>

## RTC_GUARDED_BY / RTC_RUN_ON：让编译器执行纪律

问题来了：“每份数据只在归属线程访问”是**纪律**，人是会犯错的——一不小心在错误的线程碰了某个变量，怎么办？WebRTC 的答案是**让编译器帮你查**：

```cpp
// 线程安全注解(thread_annotations.h),Clang 编译期分析
class Call {
  worker_thread_;
  std::map<...> receive_streams_ RTC_GUARDED_BY(worker_thread_);  // 声明:此数据归 worker

  void AddStream() RTC_RUN_ON(worker_thread_) {   // 声明:此函数只在 worker 跑
    receive_streams_[ssrc] = ...;                 // ✅ 合法,在归属线程
  }
};
// 若从别的线程访问 receive_streams_,或没标 RTC_RUN_ON 就碰它 → 编译告警/失败
```

<Callout type="info">
  **`RTC_GUARDED_BY(x)`、`RTC_RUN_ON(x)`、`RTC_LOCKABLE`、`RTC_EXCLUSIVE_LOCKS_REQUIRED(x)` 是 **Clang 线程安全分析**的注解（`thread_annotations.h`）——它们在**编译期**静态检查“这份数据是否只在该线程 / 持有该锁时被访问”，违反就报错。** 于是“[数据归属线程](https://daiw.org/manual/webrtc/synchronization)”这条纪律不再靠自觉，而是**编译器强制**：

  - **`RTC_GUARDED_BY(thread_or_mutex)`** —— 声明某成员“必须在持有某锁 / 在某线程时”才能访问。
  - **`RTC_RUN_ON(thread)`** —— 声明某函数“只应在某线程运行”，函数体里访问该线程的 `GUARDED_BY` 数据才合法。
  - **编译期报错** —— 写错线程访问，Clang 直接[编译不过](https://daiw.org/manual/webrtc/reading-the-source)，bug 在敲代码时就被抓住，而非线上偶现的诡异崩溃。

  这就是为什么[前面各部分](https://daiw.org/manual/webrtc/call-recap)的源码里到处是这些标注——它们是 WebRTC **把并发纪律固化进类型系统**的手段。你读源码时，`RTC_GUARDED_BY(x)` 是极有价值的信息：它直接告诉你“**这份数据归谁、该在哪访问**”，是理解一段代码线程模型的最快线索。
</Callout>

## SequenceChecker：运行期的补充检查

编译期注解很强，但有些情况静态分析覆盖不到（比如通过接口指针的间接调用）。`SequenceChecker` 补上**运行期**检查：在对象里放一个 `SequenceChecker`，方法入口 `RTC_DCHECK_RUN_ON(&sequence_checker_)`，一旦在错误的线程 / 序列上被调用，[Debug 构建](https://daiw.org/manual/webrtc/reading-the-source)直接断言崩溃，把问题暴露在测试阶段。**编译期（注解）+ 运行期（SequenceChecker）双保险**，共同守护“数据只在归属线程访问”这条铁律。

## 小结与下一章

`rtc_base` 提供跨平台同步原语 **`Mutex`**（按平台后端）、**`Event`**、**`SequenceChecker`**。但 WebRTC 的核心哲学是**用线程归属代替锁**：[规定每份数据归属某线程、只在该线程访问、跨线程靠 `PostTask`](https://daiw.org/manual/webrtc/thread-and-taskqueue)，从而[无并发、免锁](https://daiw.org/manual/webrtc/threading-model)——[架构本身即并发安全策略](https://daiw.org/manual/webrtc/threading-model)。这条纪律靠 **Clang 线程安全注解**（`RTC_GUARDED_BY`/`RTC_RUN_ON` 等，`thread_annotations.h`）在**编译期强制**、靠 **`SequenceChecker`** 在**运行期**补检——双保险。读源码时，`RTC_GUARDED_BY(x)` 是判断“数据归谁”的最快线索。

`rtc_base` 还有一样贯穿[拥塞控制](https://daiw.org/manual/webrtc/pacing)与[统计](https://daiw.org/manual/webrtc/stats-collector)的小工具——**滑动窗口测速率**。下一章看它，然后收尾第 10 部分。👉 [速率统计 RateStatistics](https://daiw.org/manual/webrtc/rate-statistics)。

## 参考资料

- [1] libwebrtc 源码.`rtc_base/synchronization/mutex.h`、`rtc_base/event.h`、`rtc_base/synchronization/sequence_checker_internal.h`。
- [2] libwebrtc 源码.`rtc_base/thread_annotations.h`（`RTC_GUARDED_BY`/`RTC_RUN_ON`）；Clang Thread Safety Analysis 文档。
- [3] 关联[线程与任务队列](https://daiw.org/manual/webrtc/thread-and-taskqueue)、[第 0 部分线程模型](https://daiw.org/manual/webrtc/threading-model)。
