WebRTC 的 PROXY - 如何解决应用中的线程乱入

        是否要保证线程安全是底层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, 就保证了对外线程安全,允许乱入, 内部的操作是在单线程有序执行。  真是了不起!


发布了83 篇原创文章 · 获赞 195 · 访问量 19万+
展开阅读全文

没有更多推荐了,返回首页

©️2019 CSDN 皮肤主题: 大白 设计师: CSDN官方博客

分享到微信朋友圈

×

扫一扫,手机浏览