是否要保证线程安全是底层SDK设计中的一个问题。 通常来说, API 线程安全是其易用的重要标志, 但是底层SDK往往是一个比较复杂的状态机, 如果声明了线程安全, 随之而来的是API的调用可能会来自任意线程。 现在的开发应用环境都已经普遍支持线程池, 对应用而言, 把任务交给线程池处理是比较方便的, 也比较自然。 这样的话, 对于底层API的调用可能是直接来自线程池的任意一个线程, 如果真的允许线程乱入, 那对于SDK自身状态维护, 是极大的挑战, 一般也很难处理好。
那么应该怎么在保持SDK 易用性的前提下, 同时又能让自己的状态维护尽量保持简单呢? 很容易想到的方法是转线程调用, 将来自任意线程的API调用, 转向SDK内部的工作线程, 这样,在应用的角度看来, 这个SDK是线程安全并且支持线程乱入的, 但是内部又保持了简单有序的操作。 在这里, WebRTC提供了一个极好的设计思路, 那就是PROXY。
WebRTC所暴露的native 接口的最重要的模块大概是PeerConnectionFactoryInterface和PeerConnectionInterface, 这两个接口都有各自对应的 PROXY. 让我们以PeerConnectionFactory的实现来分析PROXY的设计思路吧. 且看代码:
- BEGIN_SIGNALING_PROXY_MAP(PeerConnectionFactory)
- PROXY_METHOD1(void, SetOptions, const Options&)
-
-
- rtc::scoped_refptr<PeerConnectionInterface> CreatePeerConnection(
- const PeerConnectionInterface::RTCConfiguration& a1,
- const MediaConstraintsInterface* a2,
- std::unique_ptr<cricket::PortAllocator> a3,
- std::unique_ptr<DtlsIdentityStoreInterface> a4,
- PeerConnectionObserver* a5) override {
- return signaling_thread_
- ->Invoke<rtc::scoped_refptr<PeerConnectionInterface>>(
- rtc::Bind(&PeerConnectionFactoryProxy::CreatePeerConnection_ot,
- this, a1, a2, a3.release(), a4.release(), a5));
- }
- rtc::scoped_refptr<PeerConnectionInterface> CreatePeerConnection(
- const PeerConnectionInterface::RTCConfiguration& a1,
- std::unique_ptr<cricket::PortAllocator> a3,
- std::unique_ptr<DtlsIdentityStoreInterface> a4,
- PeerConnectionObserver* a5) override {
- return signaling_thread_
- ->Invoke<rtc::scoped_refptr<PeerConnectionInterface>>(
- rtc::Bind(&PeerConnectionFactoryProxy::CreatePeerConnection_ot,
- this, a1, a3.release(), a4.release(), a5));
- }
- PROXY_METHOD1(rtc::scoped_refptr<MediaStreamInterface>,
- CreateLocalMediaStream, const std::string&)
- PROXY_METHOD1(rtc::scoped_refptr<AudioSourceInterface>,
- CreateAudioSource, const MediaConstraintsInterface*)
- PROXY_METHOD1(rtc::scoped_refptr<AudioSourceInterface>,
- CreateAudioSource,
- const cricket::AudioOptions&)
- PROXY_METHOD2(rtc::scoped_refptr<VideoTrackSourceInterface>,
- CreateVideoSource,
- cricket::VideoCapturer*,
- const MediaConstraintsInterface*)
- PROXY_METHOD1(rtc::scoped_refptr<VideoTrackSourceInterface>,
- CreateVideoSource,
- cricket::VideoCapturer*)
- 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)
- PROXY_METHOD1(bool, StartRtcEventLog, rtc::PlatformFile)
- PROXY_METHOD2(bool, StartRtcEventLog, rtc::PlatformFile, int64_t)
- PROXY_METHOD0(void, StopRtcEventLog)
-
- private:
- rtc::scoped_refptr<PeerConnectionInterface> CreatePeerConnection_ot(
- const PeerConnectionInterface::RTCConfiguration& a1,
- const MediaConstraintsInterface* a2,
- cricket::PortAllocator* a3,
- DtlsIdentityStoreInterface* a4,
- PeerConnectionObserver* a5) {
- std::unique_ptr<cricket::PortAllocator> ptr_a3(a3);
- std::unique_ptr<DtlsIdentityStoreInterface> ptr_a4(a4);
- return c_->CreatePeerConnection(a1, a2, std::move(ptr_a3),
- std::move(ptr_a4), a5);
- }
-
- rtc::scoped_refptr<PeerConnectionInterface> CreatePeerConnection_ot(
- const PeerConnectionInterface::RTCConfiguration& a1,
- cricket::PortAllocator* a3,
- DtlsIdentityStoreInterface* a4,
- PeerConnectionObserver* a5) {
- std::unique_ptr<cricket::PortAllocator> ptr_a3(a3);
- std::unique_ptr<DtlsIdentityStoreInterface> ptr_a4(a4);
- return c_->CreatePeerConnection(a1, std::move(ptr_a3), std::move(ptr_a4),
- a5);
- }
- END_SIGNALING_PROXY()
BEGIN_SIGNALING_PROXY_MAP(PeerConnectionFactory)
END_SIGNALING_PROXY() 这两句声明了PeerConnectionFactoryProxy 对象。
那么什么叫作SIGNALING_PROXY呢? 还是需要继续分析代码:
- #define BEGIN_SIGNALING_PROXY_MAP(c) \
- class c##Proxy : public c##Interface { \
- protected: \
- typedef c##Interface C; \
- c##Proxy(rtc::Thread* signaling_thread, C* c) \
- : signaling_thread_(signaling_thread), c_(c) {} \
- ~c##Proxy() { \
- MethodCall0<c##Proxy, void> call( \
- this, &c##Proxy::Release_s); \
- call.Marshal(signaling_thread_); \
- } \
- \
- public: \
- static rtc::scoped_refptr<C> Create(rtc::Thread* signaling_thread, C* c) { \
- return new rtc::RefCountedObject<c##Proxy>( \
- signaling_thread, c); \
- }
这里可以看到, BEGIN_SIGNALING_PROXY_MAP(PeerConnectionFactory)实际上声明了class, 其名字为: PeerConnectionFactoryProxy, 继承自接口 PeerConnectionInterface, 为啥要继承PeerConnectionInterface呢, 道理很简单, 因为这个class是PeerConnectionFactoryProxy的代码, 当然需要实现PeerConnectionInterface所定义的借口。 PeerConnectionFactoryProxy 的构造函数需要传入一个参数: signaling_thread, 这个参数是rtc::Thread类型, 它非常关键, PeerConnectionFactory所有跟Signal相关的函数调用都会被切换到这个signaling_thread来调用. 其实现看这里:
- class SynchronousMethodCall
- : public rtc::MessageData,
- public rtc::MessageHandler {
- public:
- explicit SynchronousMethodCall(rtc::MessageHandler* proxy)
- : e_(), proxy_(proxy) {}
- ~SynchronousMethodCall() {}
-
- void Invoke(rtc::Thread* t) {
- if (t->IsCurrent()) {
- proxy_->OnMessage(NULL);
- } else {
- e_.reset(new rtc::Event(false, false));
- t->Post(this, 0);
- e_->Wait(rtc::Event::kForever);
- }
- }
SynchronousMethodCall 是这种signaling_proxy的核心实现部分, 它检查外部的调用是否来自signaling_thread, 是则执行, 否则往signaling_thread发出事件, 要求执行, 等待执行完后返回。 这里的实现逻辑虽然简单, 但是却极为有效, 对于普通对象来说 只需要简单声明一些宏,就可以生成对应的proxy, 就保证了对外线程安全,允许乱入, 内部的操作是在单线程有序执行。 真是了不起!