文章目录
1、背景
由于在webrtc 中存在多线程,而多线程的互斥和同步是工程项目中,最需要关注的和解决的问题。那在webrtc是如何解决的呢?
1.1 webrtc 3种异步处理机制
从webrtc源码学习 - 3种异步处理,中我们了解到有3中异步处理机制,可以让任务在指定的线程中执行。
1.2 代理模式
了解到上面基础之后,来思考下,如何让所有调用某个对象(类)的接口时候都是在指定的线程上执行呢?
这个也是很容易想到,代理模式。让每个接口通过代理封装异步调用,就可以实现接口在指定线程的运行。虽然可以想到使用代理模式,但是如果类很多,每个类都需要写一个代理类的情况,那太繁琐了,这也是代理模式存在缺点之一。
1.3 宏定义解决代理模式代码复用性问题
但是我们知道,要实现功能就在每个接口中,调用3种异步处理中第3种,“同步多线程调用” 的方式,每个接口实现的功能都是一样,代码没有区别,不同点,只不过是参数不同而已。
解决代码复用性的问题,一般有2种思路
- 1、宏定义方式
- 2、模板类
上面2种解决代码复用性的问题,如果接口名称统一,可以使用模板类的方式,但是本例中明显不使用,应该每个类的接口都不一样,所以只剩下了宏定义的方式
2、代理实现
2.1 示例
在之前文章分析创建PeerConnection 的时候,看到创建了pc_factory后,没有直接使用pc_factory返回,而是使用PeerConnectionFactoryProxy::Create 创建了factory返回。并且参数中有指定的线程。通过名称大家应该都了解了,PeerConnectionFactoryProxy 就是PeerConnectionFactory 的代理类。
rtc::scoped_refptr<PeerConnectionFactoryInterface>
CreateModularPeerConnectionFactory(
PeerConnectionFactoryDependencies dependencies) {
rtc::scoped_refptr<PeerConnectionFactory> pc_factory(
new rtc::RefCountedObject<PeerConnectionFactory>(
std::move(dependencies)));
return PeerConnectionFactoryProxy::Create(pc_factory->signaling_thread(),
pc_factory);
}
2.2 PeerConnectionFactoryProxy 的实现
通过1.2 和 1.3 的分析,我们明白了,PeerConnectionFactoryProxy 的实现是基于宏定义,如下
// TODO(deadbeef): Move this to .cc file and out of api/. What threads methods
// are called on is an implementation detail.
BEGIN_SIGNALING_PROXY_MAP(PeerConnectionFactory)
PROXY_SIGNALING_THREAD_DESTRUCTOR()
PROXY_METHOD1(void, SetOptions, const Options&)
PROXY_METHOD4(rtc::scoped_refptr<PeerConnectionInterface>,
CreatePeerConnection,
const PeerConnectionInterface::RTCConfiguration&,
std::unique_ptr<cricket::PortAllocator>,
std::unique_ptr<rtc::RTCCertificateGeneratorInterface>,
PeerConnectionObserver*)
PROXY_METHOD2(rtc::scoped_refptr<PeerConnectionInterface>,
CreatePeerConnection,
const PeerConnectionInterface::RTCConfiguration&,
PeerConnectionDependencies)
PROXY_CONSTMETHOD1(webrtc::RtpCapabilities,
GetRtpSenderCapabilities,
cricket::MediaType)
PROXY_CONSTMETHOD1(webrtc::RtpCapabilities,
GetRtpReceiverCapabilities,
cricket::MediaType)
PROXY_METHOD1(rtc::scoped_refptr<MediaStreamInterface>,
CreateLocalMediaStream,
const std::string&)
PROXY_METHOD1(rtc::scoped_refptr<AudioSourceInterface>,
CreateAudioSource,
const cricket::AudioOptions&)
PROXY_METHOD2(rtc::scoped_refptr<VideoTrackInterface>,
CreateVideoTrack,
const std::string&,
VideoTrackSourceInterface*)
PROXY_METHOD2(rtc::scoped_refptr<AudioTrackInterface>,
CreateAudioTrack,
const std::string&,
AudioSourceInterface*)
PROXY_METHOD2(bool, StartAecDump, rtc::PlatformFile, int64_t)
PROXY_METHOD0(void, StopAecDump)
END_PROXY_MAP()
1、宏是有格式的,Begin 开始,END 结束,其实通过上面的代码我们可以推断出,begin 中肯定包含类名和构造函数(因为需要类名拼接)
2、中间省略部分是方法
BEGIN_SIGNALING_PROXY_MAP(PeerConnectionFactory)
...
END_PROXY_MAP()
好的,我们先看下BEGIN_SIGNALING_PROXY_MAP(PeerConnectionFactory)
因为宏有几层嵌套,所以我在下面把宏的声明,放到代码下发。展开后其实代码还是非常清晰的,和我们猜测才不多。但是有几点需要注意
- 通过宏定义c##Proxy , 推导出 class c##ProxyWithInternal <c##Interface>是模板类的类型
- class c##ProxyWithInternal 继承 c##Interface
- create 的静态方法就是 new c##ProxyWithInternal的对象
- 实现了构造、析构
#define BEGIN_SIGNALING_PROXY_MAP(c) \
PROXY_MAP_BOILERPLATE(c) \
SIGNALING_PROXY_MAP_BOILERPLATE(c) \
REFCOUNTED_PROXY_MAP_BOILERPLATE(c) \
public: \
static rtc::scoped_refptr<c##ProxyWithInternal> Create( \
rtc::Thread* signaling_thread, INTERNAL_CLASS* c) { \
return new rtc::RefCountedObject<c##ProxyWithInternal>(signaling_thread, \
c); \
}
// Helper macros to reduce code duplication.
// class 声明的头部,
// typedef c##ProxyWithInternal<c##Interface> c##Proxy; 类封装的是c##Interface的指针。
#define PROXY_MAP_BOILERPLATE(c) \
template <class INTERNAL_CLASS> \
class c##ProxyWithInternal; \
typedef c##ProxyWithInternal<c##Interface> c##Proxy; \
template <class INTERNAL_CLASS> \
class c##ProxyWithInternal : public c##Interface { \
protected: \
typedef c##Interface C; \
\
public: \
const INTERNAL_CLASS* internal() const { return c_; } \
INTERNAL_CLASS* internal() { return c_; }
//
#define SIGNALING_PROXY_MAP_BOILERPLATE(c) \
protected: \
c##ProxyWithInternal(rtc::Thread* signaling_thread, INTERNAL_CLASS* c) \
: signaling_thread_(signaling_thread), c_(c) {} \
\
private: \
mutable rtc::Thread* signaling_thread_;
// Note that the destructor is protected so that the proxy can only be
// destroyed via RefCountInterface.
#define REFCOUNTED_PROXY_MAP_BOILERPLATE(c) \
protected: \
~c##ProxyWithInternal() { \
MethodCall0<c##ProxyWithInternal, void> call( \
this, &c##ProxyWithInternal::DestroyInternal); \
call.Marshal(RTC_FROM_HERE, destructor_thread()); \
} \
\
private: \
void DestroyInternal() { c_ = nullptr; } \
rtc::scoped_refptr<INTERNAL_CLASS> c_;
中间部分是函数的代理方法,我们随便找一个方法看下,其他的方法就很相似,就是为了拼装出一个函数,主要部分包括 返回值、函数名、参数。
- 第一个参数 bool 是返回类型
- StartAecDump 是函数名
- rtc::PlatformFile, int64_t 是2个参数的类型
PROXY_METHOD2(bool, StartAecDump, rtc::PlatformFile, int64_t)
宏实现,在函数的内部 调用 MethodCall2,也就是在指定线程执行,同步等待结果。具体可以看下从webrtc源码学习 - 3种异步处理,2.3 小节介绍了,跨线程的同步调用
#define PROXY_METHOD2(r, method, t1, t2) \
r method(t1 a1, t2 a2) override { \
MethodCall2<C, r, t1, t2> call(c_, &C::method, std::move(a1), \
std::move(a2)); \
return call.Marshal(RTC_FROM_HERE, signaling_thread_); \
}
3 总结
- 使用代理模式,可以将类对象在方法运行在指定线程,create 创建proxy对象,并指定线程
- 使用宏定义的方式,可以减少代码量
- 函数实现部分,使用跨线程同步访问的MethodCall 方法,底层实现是SynchronousMethodCall