线程与任务队列 · Thread 其实是个 TaskQueue
WebRTC 的并发模型建在 Thread 和 TaskQueueBase 上,而一个惊人的事实是——Thread 本身就继承自 TaskQueueBase。这一章读真实的 thread.h,讲清 PostTask/BlockingCall 怎么工作、为什么「线程就是任务队列」这个设计撑起了整个三线程模型。
线程与任务队列 · Thread 其实是个 TaskQueue
第 0 部分讲过三线程模型(signaling / worker / network),那是“有哪几条线程”。这一章讲它们在代码里到底是什么——答案藏在 thread.h 一行继承声明里,而且可能和你想的不一样。
惊人的一行:Thread 是 TaskQueueBase
读 thread.h,Thread 的定义是这样(真实源码):
// thread.h:187(真实源码)
class RTC_LOCKABLE RTC_EXPORT Thread : public TaskQueueBase {
public:
static Thread* Current(); // 拿到当前线程
// PostTask / PostDelayedTask(继承自 TaskQueueBase)
// BlockingCall(...) // 同步跨线程调用
};WebRTC 的 Thread 不是“一条裸线程”——它继承自 TaskQueueBase,本质是“一条线程 + 一个任务队列”。 也就是说,WebRTC 的线程都带着一个任务队列:你不直接在线程上“跑代码”,而是往它的队列里 PostTask(投递一个任务),线程按顺序取出来执行。这个设计影响深远:
- 跨线程 = 投递任务 —— 想让某件事在 worker 线程上做?不用手动加锁抢线程,而是
worker_thread->PostTask([]{ ... })。任务在目标线程上串行执行,天然避免了竞争。 - 线程内无需锁 —— 因为同一线程的任务串行跑,访问只属于该线程的数据(用
RTC_GUARDED_BY(worker_thread)标注)就不需要锁——这正是第 0 部分说的“用线程归属替代锁”的实现基础。 - 统一的并发模型 —— signaling / worker / network 都是这样的“线程 + 队列”,跨线程协作全靠互相
PostTask,模型高度统一。
所以“三线程模型”更准确的说法是“三个任务队列”。理解了“线程即队列”,你就懂了 WebRTC 并发的底层逻辑。
PostTask:异步投递
PostTask 是最常用的跨线程手段——异步、不等结果:
// 在任意线程调用,让 lambda 在 network 线程上异步执行
network_thread()->PostTask([this] { DoNetworkStuff(); });投递后调用方立刻返回,任务排进目标线程的队列,轮到它时执行。代理模式那章讲的“自动把调用派发到正确线程”,底层就是 PostTask。还有 PostDelayedTask(延迟执行,用于超时、定时任务如 RTCP 周期发送)。
BlockingCall:同步跨线程(慎用)
有时你需要等结果——在 A 线程调用、要拿到 B 线程算出的值。这就是 BlockingCall:
BlockingCall 让调用方阻塞等待任务在目标线程执行完再返回——方便,但危险:它会卡住调用线程**,用多了会拖慢系统,还可能死锁(A 等 B、B 又等 A)。** WebRTC 对它高度警惕,源码里专门有工具抓滥用:
ScopedDisallowBlockingCalls—— 在某段作用域内禁止任何BlockingCall(违反就断言失败),用于保护绝不该阻塞的热路径。ScopedCountBlockingCalls—— 统计一段作用域里发生了多少次阻塞调用,用于抓性能回归。
这种“给一个方便但危险的 API 配一堆护栏”的做法,很能体现 WebRTC 工程上的谨慎:能异步 PostTask 就别 BlockingCall。你读源码时看到 BlockingCall,基本都是“实在需要同步、且确认不会死锁”的地方。
TaskQueue 的多平台后端
TaskQueueBase 是抽象,真正的任务队列实现按平台分(呼应 rtc_base 跨平台的使命):
| 后端源码 | 平台 | 底层机制 |
|---|---|---|
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 要自造这层:一份并发代码,五个平台一致运行。
小结与下一章
WebRTC 的 Thread 继承自 TaskQueueBase(thread.h:187)——线程即任务队列。跨线程靠 PostTask(异步投递,任务在目标线程串行执行,故线程内数据无需锁),这是三线程模型和代理模式的实现基础。BlockingCall(同步等待)方便但危险(卡线程 / 死锁风险),源码用 ScopedDisallowBlockingCalls/ScopedCountBlockingCalls 严防滥用。TaskQueueBase 是抽象,按平台有 win/gcd/stdlib 等后端,把差异吃掉。
线程管“代码在哪跑”,下一个地基管“对象活多久”——引用计数。👉 引用计数与 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 部分线程模型、代理模式。