是否要保证线程安全是底层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&)
// Can't use PROXY_METHOD5 because unique_ptr must be moved.
// TODO(tommi,hbos): Use of templates to support unique_ptr?
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, 就保证了对外线程安全,允许乱入, 内部的操作是在单线程有序执行。 真是了不起!