# 线程与任务队列 · Thread 其实是个 TaskQueue

> WebRTC 的并发模型建在 Thread 和 TaskQueueBase 上，而一个惊人的事实是——Thread 本身就继承自 TaskQueueBase。这一章读真实的 thread.h，讲清 PostTask/BlockingCall 怎么工作、为什么「线程就是任务队列」这个设计撑起了整个三线程模型。

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

# 线程与任务队列 · Thread 其实是个 TaskQueue

[第 0 部分讲过三线程模型](https://daiw.org/manual/webrtc/threading-model)（signaling / worker / network），那是“有哪几条线程”。这一章讲**它们在代码里到底是什么**——答案藏在 `thread.h` 一行继承声明里，而且可能和你想的不一样。

## 惊人的一行：Thread 是 TaskQueueBase

读 `thread.h`，`Thread` 的定义是这样（真实源码）：

```cpp
// thread.h:187(真实源码)
class RTC_LOCKABLE RTC_EXPORT Thread : public TaskQueueBase {
 public:
  static Thread* Current();           // 拿到当前线程
  // PostTask / PostDelayedTask(继承自 TaskQueueBase)
  // BlockingCall(...)                // 同步跨线程调用
};
```

<Callout type="info">
  **WebRTC 的 `Thread` **不是**“一条裸线程”——它继承自 `TaskQueueBase`，本质是“一条线程 + 一个任务队列”。** 也就是说，WebRTC 的线程都带着一个**任务队列**：你不直接在线程上“跑代码”，而是往它的队列里 **`PostTask`**（投递一个任务），线程按顺序取出来执行。这个设计影响深远：

  - **跨线程 = 投递任务** —— 想让某件事在 [worker 线程](https://daiw.org/manual/webrtc/threading-model)上做？不用手动加锁抢线程，而是 `worker_thread->PostTask([]{ ... })`。任务在目标线程上**串行**执行，天然避免了竞争。
  - **线程内无需锁** —— 因为同一线程的任务串行跑，访问只属于该线程的数据（[用 `RTC_GUARDED_BY(worker_thread)` 标注](https://daiw.org/manual/webrtc/synchronization)）就**不需要锁**——这正是[第 0 部分说的“用线程归属替代锁”](https://daiw.org/manual/webrtc/threading-model)的实现基础。
  - **统一的并发模型** —— [signaling / worker / network](https://daiw.org/manual/webrtc/threading-model) 都是这样的“线程 + 队列”，跨线程协作全靠互相 `PostTask`，模型高度统一。

  所以“[三线程模型](https://daiw.org/manual/webrtc/threading-model)”更准确的说法是“**三个任务队列**”。理解了“线程即队列”，你就懂了 WebRTC 并发的底层逻辑。
</Callout>

## PostTask：异步投递

`PostTask` 是最常用的跨线程手段——**异步、不等结果**：

```cpp
// 在任意线程调用,让 lambda 在 network 线程上异步执行
network_thread()->PostTask([this] { DoNetworkStuff(); });
```

投递后调用方**立刻返回**，任务排进目标线程的队列，轮到它时执行。[代理模式那章](https://daiw.org/manual/webrtc/proxy-pattern)讲的“[自动把调用派发到正确线程](https://daiw.org/manual/webrtc/proxy-pattern)”，底层就是 `PostTask`。还有 `PostDelayedTask`（延迟执行，用于[超时](https://daiw.org/manual/webrtc/connection-and-checks)、[定时任务](https://daiw.org/manual/webrtc/rtp-rtcp-module)如 RTCP 周期发送）。

## BlockingCall：同步跨线程（慎用）

有时你需要**等结果**——在 A 线程调用、要拿到 B 线程算出的值。这就是 `BlockingCall`：

<Callout type="warn">
  **`BlockingCall` 让调用方**阻塞等待**任务在目标线程执行完再返回——方便，但危险：它会**卡住调用线程**，用多了会拖慢系统，还可能[死锁](https://daiw.org/manual/webrtc/threading-model)（A 等 B、B 又等 A）。** WebRTC 对它高度警惕，源码里专门有工具抓滥用：

  - **`ScopedDisallowBlockingCalls`** —— 在某段作用域内**禁止**任何 `BlockingCall`（违反就断言失败），用于保护绝不该阻塞的热路径。
  - **`ScopedCountBlockingCalls`** —— **统计**一段作用域里发生了多少次阻塞调用，用于[抓性能回归](https://daiw.org/manual/webrtc/reading-the-source)。

  这种“给一个方便但危险的 API 配一堆护栏”的做法，很能体现 WebRTC 工程上的谨慎：能异步 `PostTask` 就别 `BlockingCall`。你读源码时看到 `BlockingCall`，基本都是“实在需要同步、且确认不会死锁”的地方。
</Callout>

## TaskQueue 的多平台后端

`TaskQueueBase` 是**抽象**，真正的任务队列实现按平台分（呼应 [rtc_base 跨平台的使命](https://daiw.org/manual/webrtc/rtc-base-overview)）：

| 后端源码 | 平台 | 底层机制 |
| --- | --- | --- |
| `task_queue_win.h` | Windows | IOCP / 消息队列 |
| `task_queue_gcd.h` | macOS / iOS | Grand Central Dispatch |
| `task_queue_stdlib.h` | 通用 | `std::thread` + 条件变量 |
| `task_queue_libevent`(Linux) | Linux/Android | libevent |

上层代码只对着 `TaskQueueBase` 编程，`PostTask` 在哪个平台都一样——**平台差异被 `rtc_base` 吃掉了**。这就是[为什么 WebRTC 要自造这层](https://daiw.org/manual/webrtc/rtc-base-overview)：一份并发代码，五个平台一致运行。

```mermaid
flowchart TD
    CODE["上层代码:thread->PostTask(...)"] --> TQ["TaskQueueBase(抽象)"]
    TQ --> WIN["task_queue_win(IOCP)"]
    TQ --> GCD["task_queue_gcd(GCD)"]
    TQ --> STD["task_queue_stdlib(std::thread)"]
    WIN --> OS1["Windows"]
    GCD --> OS2["macOS/iOS"]
    STD --> OS3["其他"]
```

## 小结与下一章

WebRTC 的 **`Thread` 继承自 `TaskQueueBase`**（`thread.h:187`）——**线程即任务队列**。跨线程靠 **`PostTask`**（异步投递，任务在目标线程串行执行，故[线程内数据无需锁](https://daiw.org/manual/webrtc/synchronization)），这是[三线程模型](https://daiw.org/manual/webrtc/threading-model)和[代理模式](https://daiw.org/manual/webrtc/proxy-pattern)的实现基础。**`BlockingCall`**（同步等待）方便但危险（[卡线程 / 死锁风险](https://daiw.org/manual/webrtc/threading-model)），源码用 `ScopedDisallowBlockingCalls`/`ScopedCountBlockingCalls` 严防滥用。`TaskQueueBase` 是抽象，[按平台](https://daiw.org/manual/webrtc/rtc-base-overview)有 win/gcd/stdlib 等后端，把差异吃掉。

线程管“代码在哪跑”，下一个地基管“对象活多久”——**引用计数**。👉 [引用计数与 scoped_refptr](https://daiw.org/manual/webrtc/scoped-refptr)。

## 参考资料

- [1] libwebrtc 源码.`thread.h`（`Thread : public TaskQueueBase`、`Current`、`BlockingCall`）、`api/task_queue/task_queue_base.h`。
- [2] libwebrtc 源码.`task_queue_win.h`、`task_queue_gcd.h`、`task_queue_stdlib.h`。
- [3] 关联[第 0 部分线程模型](https://daiw.org/manual/webrtc/threading-model)、[代理模式](https://daiw.org/manual/webrtc/proxy-pattern)。
