引用计数 · scoped_refptr 与对象的生死
WebRTC 的对象在多线程间传来传去,谁负责销毁?答案是引用计数。这一章读 scoped_refptr.h / ref_counted_object.h,讲清 RefCountInterface、RefCountedObject、make_ref_counted、scoped_refptr 这套机制,以及它为什么是异步多线程代码的生命线。
引用计数 · scoped_refptr 与对象的生死
上一章说,WebRTC 靠 PostTask 在线程间投递任务。但这带来一个尖锐问题:一个对象被投递到别的线程异步使用,执行时它还活着吗?会不会已经被原线程销毁了?WebRTC 的答案是引用计数——这一章看它。
问题:异步世界里,谁来销毁对象
同步单线程代码里,对象生死很清楚:出了作用域就析构。但 WebRTC 是异步多线程的:
在异步代码里,一个对象可能被多个线程、多个回调同时引用,“谁最后用完谁负责销毁”用手动 delete 根本管不清——所以 WebRTC 用引用计数**:对象记录“有多少人正引用我”,降到 0 才自我销毁。** 想象 PeerConnection 被应用持有、又被内部若干组件引用、还被投递到别的线程的任务捕获——它到底何时能销毁?没有一个中心知道全部引用。引用计数把这个判断分散化:每个持有者“登记”(计数 +1)、“注销”(计数 -1),对象自己在计数归 0 时析构。没有谁需要知道全局,对象自己管自己的生死。 这是异步多线程代码管理生命周期的经典解法(也是 std::shared_ptr 的思路)。
三个零件:接口、实现、智能指针
WebRTC 的引用计数由三部分组成(比 shared_ptr 更“侵入式”——计数长在对象自己身上):
1. RefCountInterface:计数接口
// api/ref_count.h(节选)
class RefCountInterface {
public:
virtual void AddRef() const = 0;
virtual RefCountReleaseStatus Release() const = 0; // 返回是否"刚丢掉最后一个引用"
};任何想被引用计数管理的类,实现这个接口:AddRef 计数 +1,Release 计数 -1 并在归 0 时返回 kDroppedLastRef。
2. RefCountedObject<T>:自动加计数的实现
你不用手写计数逻辑,RefCountedObject<T> 帮你把原子计数混入任意类 T(真实源码 ref_counted_object.h:21):
// ref_counted_object.h(节选)
template <class T>
class RefCountedObject : public T { // 继承 T,给它加上引用计数
RefCountReleaseStatus Release() const override {
// 原子减,归 0 时 delete this
if (status == RefCountReleaseStatus::kDroppedLastRef) { delete this; }
...
}
mutable webrtc::webrtc_impl::RefCounter ref_count_{0}; // 原子计数器
};通常用工厂函数 make_ref_counted<MyClass>(args...) 创建——它 new 一个 RefCountedObject<MyClass> 并返回 scoped_refptr。原子计数保证多线程 AddRef/Release 安全。
3. scoped_refptr<T>:RAII 智能指针
手动 AddRef/Release 容易忘、容易漏——所以几乎没人手动调。你用 scoped_refptr<T>,它在构造 / 拷贝时自动 AddRef、析构时自动 Release(真实源码 scoped_refptr.h):
// scoped_refptr.h(节选,精简)
template <typename T>
class scoped_refptr {
scoped_refptr(const scoped_refptr& r) : ptr_(r.get()) { if (ptr_) ptr_->AddRef(); }
~scoped_refptr() { if (ptr_) ptr_->Release(); } // 出作用域自动 -1
scoped_refptr(scoped_refptr&& r) noexcept : ptr_(r.release()) {} // 移动不加计数
T* get() const { return ptr_; }
private:
T* ptr_;
};scoped_refptr<T> 是 WebRTC 版的 shared_ptr——拷贝就 +1、析构就 -1、移动零成本,你只管传递它,计数全自动。 关键行为:
- 拷贝 = 多一个引用 ——
scoped_refptr传参 / 赋值,计数 +1,对象因此“多活一会儿”。 - 析构 = 少一个引用 —— 出作用域自动 -1,最后一个走的负责销毁。
- 移动 = 转移所有权 ——
std::move一个scoped_refptr不改计数(release()交出裸指针),零开销。
于是投递到别的线程的任务只要捕获一个 scoped_refptr,就等于给对象续了命——任务没跑完,计数就不为 0,对象就不会被销毁。引用计数 + scoped_refptr 是 WebRTC 异步安全的生命线。
为什么到处是 scoped_refptr
现在回头看前面各部分,就懂了:PeerConnectionInterface、MediaStreamTrackInterface、RtpSenderInterface、RTCCertificate……WebRTC 的 API 对象几乎清一色用 scoped_refptr 返回和传递。原因就是上面说的:这些对象跨线程、被多方持有、生命周期不确定,必须用引用计数管理。CreatePeerConnection 返回 scoped_refptr<PeerConnectionInterface>——你现在知道这个类型不是随便选的,而是异步架构的必然。
小结与下一章
WebRTC 用引用计数管理异步多线程下的对象生死:对象记“多少人引用我”、归 0 自毁,免去了“谁负责 delete”的难题。三个零件:RefCountInterface(AddRef/Release 接口)、RefCountedObject<T>(把原子计数混入任意类,归 0 delete this,配 make_ref_counted)、scoped_refptr<T>(RAII 智能指针:拷贝 +1、析构 -1、移动零成本)。跨线程任务捕获 scoped_refptr 即给对象续命——这是 WebRTC 异步安全的生命线,也是API 对象清一色 scoped_refptr 的原因。
对象在系统里传递时,还有一样东西天天在传——媒体数据缓冲。它多到必须“零拷贝”。下一章看 WebRTC 的缓冲设计。👉 零拷贝缓冲。