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

文章目录

        • PlatformThread使用示例
          • 示例-创建执行一次的线程
          • 示例-创建可以执行多次的线程
          • 示例-高优先级线程先运行
        • 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 

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 

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 
#include 

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;
}

WebRTC源码分析之平台线程-PlatformThread_第1张图片
在这里插入图片描述
前3秒一直运行threadLow线程,3秒过后threadHigh线程生成,由于其优先级高,所以threadHigh线程会一直霸占CPU的使用权,而threadLow线程无法获取CPU使用权。

#include "rtc_base/platform_thread.h"
#include 
#include 

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 

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;
}

WebRTC源码分析之平台线程-PlatformThread_第2张图片
WebRTC源码分析之平台线程-PlatformThread_第3张图片
在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的使用权。

你可能感兴趣的:(WebRTC源码分析,webrtc,c++)