WebRTC源码分析之平台线程-PlatformThread


WebRTC是跨多种平台的,为了方便线程的使用,把各个平台的线程封装成了 PlatformThread类。 PlatformThread封装的线程是有优先级的,线程的执行并不是按时间片轮询执行的,而是高优先级的线程会一直在CPU中执行,直到有更高优先级的线程到来或主动让出CPU。

PlatformThread使用示例

工程

先创建一个工程用于运行示例。如何创建工程,参考《WebRTC源码分析之工程-project》,在src\examples\BUILD.gn中添加如下内容:

rtc_executable("webrtc_learn"){
    testonly = true
  sources = [
    "webrtclearn/main.cc"         
  ]
  deps = [
    "../rtc_base:rtc_base"       
  ]
}
示例-创建执行一次的线程
#include "rtc_base/platform_thread.h"
#include <iostream>

using namespace std;

/*子线程运行的函数*/
void func(void* arg)
{
    cout << "child thread  arg = " << (char*)arg << endl;
}

int main()
{
    /*创建线程对象,传入执行函数、参数及线程名称。*/
    rtc::PlatformThread th(func, (void *)"hello world", "child thread");

    /*创建子线程并运行*/
    th.Start();

    /*回收子线程*/
    th.Stop();

    cout << "main thread" << endl;

    return 0;
}

在这里插入图片描述
当线程执行函数没有返回值时,主线程中th.Stop会一直阻塞的等待回收子线程,并不会杀死子线程。

示例-创建可以执行多次的线程
#include "rtc_base/platform_thread.h"
#include <iostream>

using namespace std;

bool func(void* arg)
{
    static int count = 0;

    Sleep(1000);   /*睡眠1s*/
    count++;

    cout << "count = " << count << endl;

    /*子线程不会主动退出*/
    return true;   
}

int main()
{
    rtc::PlatformThread th(func, nullptr,nullptr);

    th.Start();

    Sleep(5000);   /*睡眠5s*/
    
    /*杀死子线程*/
    th.Stop();

    cout << "main thread" << endl;

    return 0;
}

当线程执行函数的返回值是bool类型时,返回false时子线程主动退出,返回true时会再次执行函数。主线程中th.Stop会主动杀死正在执行的线程,并回收子线程。

从源码中的命名中来看,可能这种使用方式被废弃了。

示例-高优先级线程先运行

这个示例在Ubuntu上运行

#include "rtc_base/platform_thread.h"
#include <iostream>
#include <unistd.h>

using namespace std;

void func(void* arg)
{
    while (1)
    {
        cout << "func()..." << endl;
    }
}

void foo(void* arg)
{
    while (1)
    {
        cout << "foo().." << endl;
    }
}

int main()
{
    rtc::PlatformThread threadLow(func, nullptr,"low",rtc::kLowPriority);
    rtc::PlatformThread threadHigh(foo, nullptr, "high",rtc::kHighPriority);

    /*低优先级的线程先执行*/
    thLow.Start();

    sleep(3);   
    
    /*高优先级的线程创建后,会独占CPU的使用。*/
    thHigh.Start();

    thLow.Stop();
    thHigh.Stop();

    return 0;
}

在这里插入图片描述
在这里插入图片描述
前3秒一直运行threadLow线程,3秒过后threadHigh线程生成,由于其优先级高,所以threadHigh线程会一直霸占CPU的使用权,而threadLow线程无法获取CPU使用权。

#include "rtc_base/platform_thread.h"
#include <iostream>
#include <unistd.h>

using namespace std;

void func(void* arg)
{
    while (1)
    {
        cout << "func()..." << endl;
    }
}

void foo(void* arg)
{
    while (1)
    {
        cout << "foo().." << endl;
    }
}

int main()
{
    rtc::PlatformThread threadLow(func, nullptr,"low",rtc::kLowPriority);
    rtc::PlatformThread threadHigh(foo, nullptr, "high",rtc::kHighPriority);

    /*线程优先级高,会一直霸占CPU,之后创建的低优先级线程无法占用CPU。*/
    thHigh.Start();

    sleep(3);   
    
    /*优先级太低,无法获取CPU的使用权。*/
    thLow.Start();

    thLow.Stop();
    thHigh.Stop();

    return 0;
}

在这里插入图片描述
threadHigh线程先创建会一直霸占CPU,一直打印foo()…。3秒后创建的threadLow线程由于优先级较低,所以一直无法获取CPU的执行权。

PlatformThread源码分析

PlatformThread类所在文件的位置:src\rtc_base\platform_thread.h platform_thread.cc

数据成员
/*线程的入口函数类型*/
typedef bool (*ThreadRunFunctionDeprecated)(void*);
typedef void (*ThreadRunFunction)(void*);

/*保存着线程入口函数*/
ThreadRunFunctionDeprecated const run_function_deprecated_ = nullptr;
ThreadRunFunction const run_function_ = nullptr;

/*线程的优先级*/
const ThreadPriority priority_ = kNormalPriority;

/*线程入口函数的参数*/
void* const obj_;

/*线程的名字*/
const std::string name_;   

/*记录主线程*/
rtc::ThreadChecker thread_checker_;

/*记录子线程*/
rtc::ThreadChecker spawned_thread_checker_;

/*线程是否结束*/
volatile int stop_flag_ = 0;  

/*线程句柄*/
pthread_t thread_ = 0;   

PlatformThread类的有些成员函数只能在主线程中执行,如PlatformThread对象的创建和析构只能在主线程中执行,有些成员函数只能在子线程执行,所以需要记录主线程和子线程。thread_checker_用于记录主线程,spawned_thread_checker_用于记录子线程。在定义ThreadChecker对象的线程中会记录下线程,调用Detach()成员函数会重置记录的线程,再次调用IsCurrent()时,会记录当前线程。

构造器和析构器
PlatformThread::PlatformThread(ThreadRunFunction func,void* obj,
absl::string_view thread_name,ThreadPriority priority)
    : run_function_(func), priority_(priority), obj_(obj),   
      name_(thread_name) 
{
  RTC_DCHECK(func);
  RTC_DCHECK(!name_.empty());

  RTC_DCHECK(name_.length() < 64);

  /*重置*/
  spawned_thread_checker_.Detach();
}

在创建PlatformThread类对象时,需要记录线程相关的信息,线程入口函数入口函数参数线程名称优先级

另一个构造器只是线程入口函数的返回值是bool类型,其他都一样。

spawned_thread_checker_在定义时记录的是主线程,现在调用Detach()函数表示重置,下次在子线程中调用Is_Current()函数时记录子线程。

PlatformThread::~PlatformThread() 
{
  /*必须在主线程中调用析构器*/
  RTC_DCHECK(thread_checker_.IsCurrent());
}

PlatformThread对象的创建在主线程中,则释放也必须在主线程中。

设置线程的属性
bool PlatformThread::IsRunning() const 
{
  /*需要在主线程中调用*/
  RTC_DCHECK(thread_checker_.IsCurrent());

  return thread_ != 0;
}

判断子线程是否创建

bool PlatformThread::SetPriority(ThreadPriority priority) 
{
#if RTC_DCHECK_IS_ON
  if (run_function_) 
  {
    RTC_DCHECK(spawned_thread_checker_.IsCurrent());
  } 
  else 
  {
    RTC_DCHECK(thread_checker_.IsCurrent());
    RTC_DCHECK(IsRunning());
  }
#endif

  /*设置线程的调度方式*/
  const int policy = SCHED_FIFO;
    
  /*获取实时优先级的最小值*/
  const int min_prio = sched_get_priority_min(policy);  
    
  /*获取实时优先级的最大值*/
  const int max_prio = sched_get_priority_max(policy);  
  if (min_prio == -1 || max_prio == -1) 
  {
    return false;
  }

  /*保证有4个优先级*/
  if (max_prio - min_prio <= 2)
    return false;

  sched_param param;
  const int top_prio = max_prio - 1;
  const int low_prio = min_prio + 1;
    
  /*将自定义的优先级映射到系统的优先级*/
  switch (priority) 
  {
    case kLowPriority:
      param.sched_priority = low_prio;
      break;
    case kNormalPriority:
      param.sched_priority = (low_prio + top_prio - 1) / 2;
      break;
    case kHighPriority:
      param.sched_priority = std::max(top_prio - 2, low_prio);
      break;
    case kHighestPriority:
      param.sched_priority = std::max(top_prio - 1, low_prio);
      break;
    case kRealtimePriority:
      param.sched_priority = top_prio;
      break;
  }
         /*设置线程调度方式,和线程的优先级。*/
  return pthread_setschedparam(thread_, policy, &param) == 0;
}

SCHED_FIFO是实时调度策略,linux默认的线程调度策略是分时调度策略(SCHED_OTHER)。

在创建线程时指定采用SCHED_FIFO,并设置优先级。如果线程没有等待资源,则将该任务加到就绪队列中,调度程序遍历就绪队列,根据优先级计算调度权值选择权值最高的任务使用CPU,该任务将一直占用CPU,直到优先级更高的任务就绪或主动放弃。

线程的入口函数
void* PlatformThread::StartThread(void* param) 
{
  /*执行传入的线程*/
  static_cast<PlatformThread*>(param)->Run();
  return 0;
}

StartThread()函数是类的静态函数,不需要任何对象就可以调用。这是子线程运行的第一个函数,也是线程的入口函数,在这个函数中会继续调用其他成员函数,但现在还没有执行用户指定的函数。

void PlatformThread::Run() 
{
  /*只能在子线程中运行本函数*/
  RTC_DCHECK(spawned_thread_checker_.IsCurrent());

  /*设置线程的名字*/
  rtc::SetCurrentThreadName(name_.c_str());

  /*若用户提供的函数没有返回值*/
  if (run_function_) 
  {
    /*设置线程的属性*/
    SetPriority(priority_);
    
    /*运行用户指定的函数*/
    run_function_(obj_);     
      
    return; /*退出线程*/
  }

#if RTC_DCHECK_IS_ON
  static const int kMaxLoopCount = 1000;
  static const int kPeriodToMeasureMs = 100;
  int64_t loop_stamps[kMaxLoopCount] = {};
  int64_t sequence_nr = 0;
#endif

  do 
  { 
    /*用户提供的函数返回值是bool类型时*/
    if (!run_function_deprecated_(obj_))   /*运行用户提供的函数*/
      break;   /*若函数返回false时,线程会退出。*/
      
    /*若函数返回true,则线程会再次调用用户提供的函数。*/ 
      
#if RTC_DCHECK_IS_ON
    /*以下代码用于判断线程是否执行过于频繁,*/
    /*若用户提供的函数执行1000次花费的时间小于100ms时,断言失败。*/
      
    auto id = sequence_nr % kMaxLoopCount;
    
    /*每次执行都记录执行的时间*/
    loop_stamps[id] = rtc::TimeMillis();

    /*执行1000次以后,每次都判断最近1000次花费的时间是否小于100ms。*/
    if (sequence_nr > kMaxLoopCount)
    {
      auto compare_id = (id + 1) % kMaxLoopCount;

      auto diff = loop_stamps[id] - loop_stamps[compare_id];
      
      RTC_DCHECK_GE(diff, 0);

      /*若调用1000次花费的时间小于100ms,说明调用过于频繁,断言失败。*/
      if (diff < kPeriodToMeasureMs) 
      {
        RTC_NOTREACHED() << "This thread is too busy: " << name_ 
                         << " " << diff << "ms sequence=" 
                         << sequence_nr << " " << loop_stamps[id] 
                         << " vs " << loop_stamps[compare_id] << ", " 
                         << id << " vs " << compare_id;
      }
    }
    
    /*记录调用的次数*/
    ++sequence_nr;
#endif

    /*主动让出CPU的使用*/
    static const struct timespec ts_null = {0};
    nanosleep(&ts_null, nullptr);   
      
  } while (!AtomicOps::AcquireLoad(&stop_flag_));   /*是否退出线程*/
}

这是子线程执行的第二个函数,在这个函数中会调用用户提供的函数。用户可以提供两种函数,一种是无返回值,另一种是返回bool值的函数。

对于无返回值的函数,线程只执行一次函数,函数执行完毕后,线程退出。

对于返回bool的函数,若返回false,则退出线程,若返回true,则子线程会一直调用该函数,直到返回false或者主线程中调用Stop()函数将其杀死。子线程每执行一次函数,会让出CPU,等待下次调度。当子线程最近调用1000次花费的时间小于100毫秒时,说明使用过于频繁,会断言失败,终止进程。

子线程主动让出CPU的使用权,使用的是sleep(0),这个在《WebRTC源码分析之锁-CriticalSection》有介绍。

下面的示例展示了,线程调用过于频繁,被终止了。

#include "rtc_base/platform_thread.h"
#include <iostream>

using namespace std;

bool func(void* arg)
{
    /*什么也不做*/

    return true;   /*重复执行本函数*/
}

int main()
{
    rtc::PlatformThread th(func, nullptr,"child thread");

    th.Start();

    Sleep(1000 * 1000);   /*睡眠1000s*/

    th.Stop();    

    return 0;
}

在这里插入图片描述
在这里插入图片描述
在1ms的时间内,线程被调用了超过1000次,调用过于频繁,进程被终止掉了。

创建线程
struct ThreadAttributes 
{
  ThreadAttributes() { pthread_attr_init(&attr); }
  ~ThreadAttributes() { pthread_attr_destroy(&attr); }
    
  pthread_attr_t* operator&() { return &attr; }
    
  pthread_attr_t attr;
};

对线程属性的封装,线程属性的初始化和销毁交给构造器和析构器完成,这样只需定义属性,无需主动释放属性,在属性对象离开作用域时会主动销毁属性。

重载了operator&()用于返回属性的地址。封装以后,使用会方便很多。

void PlatformThread::Start() 
{
  /*线程的创建必须在主线程中进行*/
  RTC_DCHECK(thread_checker_.IsCurrent());
  RTC_DCHECK(!thread_) << "Thread already started?";

  ThreadAttributes attr;
  
  /*设置子线程栈大小为1MB*/
  pthread_attr_setstacksize(&attr, 1024 * 1024);

  /*创建线程并运行子线程*/
  RTC_CHECK_EQ(0, pthread_create(&thread_, &attr, &StartThread, this));
}

Start()函数是对外提供的接口,用于创建线程并执行线程入口函数。

回收线程
void PlatformThread::Stop() 
{
  /*回收子线程,必须在主线程运行。*/
  RTC_DCHECK(thread_checker_.IsCurrent());
  
  /*没有创建线程,则无需回收。*/
  if (!IsRunning())
    return;

  /*杀死子线程*/
  if (!run_function_)
    RTC_CHECK_EQ(1, AtomicOps::Increment(&stop_flag_));

  /*回收线程*/
  RTC_CHECK_EQ(0, pthread_join(thread_, nullptr));    

  /*重置*/
  if (!run_function_)
    AtomicOps::ReleaseStore(&stop_flag_, 0);
  
  /*重置*/
  thread_ = 0;
  
  /*子线程被释放了,重置后等待下次记录新的子线程。*/
  spawned_thread_checker_.Detach();
}

子线程执行无返回值的函数,主线程会阻塞在pthread_join()函数,直到子线程执行完毕才回收子线程。

子线程执行返回值为bool的函数,主线程调用Stop()函数,会杀死正在运行子线程,准确的说是子线程执行完毕函数后,即使返回true,也不会再次执行函数了。

小结

本文介绍了WebRTC中封装的线程,根据源码中的命名,貌似返回值为bool类型的函数被废弃了。执行返回值为void函数的子线程,可以被设置优先级,优先级高的线程可以一直霸占CPU的使用权。

  • 0
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
WebRTC(Web Real-Time Communication)是一个开源项目,它提供了在浏览器中实现实时音视频通信的技术。下面是对WebRTC源码的简要分析WebRTC源码主要分为以下几个模块: 1. 信令(Signaling)模块:负责建立和维护通信的连接。它使用WebSocket或者其他协议进行通信,包括传输SDP(Session Description Protocol)和ICE(Interactive Connectivity Establishment)信息。 2. 媒体(Media)模块:处理音视频数据的采集、编码、解码和传输。媒体模块使用WebRTC提供的API,通过WebRTC的PeerConnection建立点对点的媒体通信。 3. 网络(Networking)模块:处理网络传输相关的功能,例如NAT穿越、ICE候选地址的收集和选择、STUN和TURN服务器的使用等。 4. 安全(Security)模块:处理加密和身份验证相关的功能,确保通信过程的安全性和隐私性。 5. SDP解析(SDP Parsing)模块:解析和生成SDP信息,SDP包含了关于媒体会话的描述和参数。 6. ICE代理(ICE Agent)模块:负责管理ICE协议的运行,处理候选地址的收集和选择,以及NAT穿越等功能。 7. RTP/RTCP模块:处理音视频的实时传输协议(RTP)和实时传输控制协议(RTCP),包括数据包的发送和接收、丢包恢复、拥塞控制等。 8. 编解码器(Codec)模块:负责音视频数据的编码和解码,WebRTC支持一系列开源编解码器,如VP8、VP9、H.264等。 这些模块之间相互协作,实现了基于浏览器的实时音视频通信。WebRTC源码使用C++语言编写,涉及到了底层的网络和媒体处理,同时也提供了一系列的API供开发者使用。 请注意,由于WebRTC源码较为庞大,这里只是简要地介绍了主要模块,实际的源码分析需要深入研究和阅读源码

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值