工程
先创建一个工程用于运行示例。如何创建工程,参考《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;
}
前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类所在文件的位置: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, ¶m) == 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;
}
在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的使用权。