webrtc源码学习 - PROXY 类对象跨线程同步问题

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)
因为宏有几层嵌套,所以我在下面把宏的声明,放到代码下发。展开后其实代码还是非常清晰的,和我们猜测才不多。但是有几点需要注意

  1. 通过宏定义c##Proxy , 推导出 class c##ProxyWithInternal <c##Interface>是模板类的类型
  2. class c##ProxyWithInternal 继承 c##Interface
  3. create 的静态方法就是 new c##ProxyWithInternal的对象
  4. 实现了构造、析构
#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_;

中间部分是函数的代理方法,我们随便找一个方法看下,其他的方法就很相似,就是为了拼装出一个函数,主要部分包括 返回值、函数名、参数。

  1. 第一个参数 bool 是返回类型
  2. StartAecDump 是函数名
  3. 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
  • 1
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值