1. 前言
TaskQueueStdlib
类是WebRTC任务队列机制的核心类,也是整个任务队列的标准库,在阅读本文之前,需要对TaskQueueBase
类有一定了解。
可以参考这篇文章WebRTC源码分析-TaskQueue(任务队列)-TaskQueueBase
WebRTC版本:M84
2. 正文
2.0. 预说明:TaskQueuePriorityToThreadPriority
rtc::ThreadPriority TaskQueuePriorityToThreadPriority(
TaskQueueFactory::Priority priority) {
switch (priority) {
case TaskQueueFactory::Priority::HIGH:
return rtc::kRealtimePriority;
case TaskQueueFactory::Priority::LOW:
return rtc::kLowPriority;
case TaskQueueFactory::Priority::NORMAL:
return rtc::kNormalPriority;
default:
RTC_NOTREACHED();
return rtc::kNormalPriority;
}
}
此函数用于将任务队列的优先级转换为线程的优先级。
TaskQueueFactory::Priority
定义如下:
enum class Priority { NORMAL = 0, HIGH, LOW };
2.1. TaskQueueStdlib
类
path:
rtc_base\task_queue_stdlib.h
rtc_base\task_queue_stdlib.cc
2.1.1. 类声明
class TaskQueueStdlib final : public TaskQueueBase {
public:
TaskQueueStdlib(absl::string_view queue_name, rtc::ThreadPriority priority);
~TaskQueueStdlib() override = default;
void Delete() override;
void PostTask(std::unique_ptr<QueuedTask> task) override;
void PostDelayedTask(std::unique_ptr<QueuedTask> task,
uint32_t milliseconds) override;
private:
using OrderId = uint64_t;
struct DelayedEntryTimeout {
int64_t next_fire_at_ms_{};
OrderId order_{};
bool operator<(const DelayedEntryTimeout& o) const {
return std::tie(next_fire_at_ms_, order_) <
std::tie(o.next_fire_at_ms_, o.order_);
}
};
struct NextTask {
bool final_task_{false};
std::unique_ptr<QueuedTask> run_task_;
int64_t sleep_time_ms_{};
};
NextTask GetNextTask();
static void ThreadMain(void* context);
void ProcessTasks();
void NotifyWake();
rtc::Event started_;
rtc::Event stopped_;
rtc::Event flag_notify_;
rtc::PlatformThread thread_;
rtc::CriticalSection pending_lock_;
bool thread_should_quit_ RTC_GUARDED_BY(pending_lock_){false};
OrderId thread_posting_order_ RTC_GUARDED_BY(pending_lock_){};
std::queue<std::pair<OrderId, std::unique_ptr<QueuedTask>>> pending_queue_
RTC_GUARDED_BY(pending_lock_);
std::map<DelayedEntryTimeout, std::unique_ptr<QueuedTask>> delayed_queue_
RTC_GUARDED_BY(pending_lock_);
};
可以看出TaskQueueStdlib
的public
成员除构造函数和析构函数之外只有三个函数,private
成员都是为了实现public
方法而定义的,所以后续会先分析其私有成员,最后再分析三个public
成员函数的具体实现方式。
2.1.2. 私有成员实现
此处使用using
语句,类似于typedef
,类型OrderId
实际上就是uint64_t
。
2.1.2.1. 私有数据成员
// 表示该线程是否已经开始
rtc::Event started_;
// 表示该线程是否已经结束
rtc::Event stopped_;
// 每当有新的任务等待时,就会发出信号
rtc::Event flag_notify_;
// 表示被分配用于处理任务的活动工作线程
rtc::PlatformThread thread_;
// 对于存在多个线程访问的数据需要上锁
rtc::CriticalSection pending_lock_;
// 表示工作线程是否需要现在关闭
bool thread_should_quit_ RTC_GUARDED_BY(pending_lock_){false};
// 保存下一个任务的序号,用于将其放入一个挂起的队列中
OrderId thread_posting_order_ RTC_GUARDED_BY(pending_lock_){};
// 需要在工作线程中按照先进先出的队列顺序处理的待办任务列表
std::queue<std::pair<OrderId, std::unique_ptr<QueuedTask>>> pending_queue_
RTC_GUARDED_BY(pending_lock_);
// 需要在工作线程中延迟一段时间再处理的待办任务列表,如果两个任务在相同的时间间隔内
// 发生,那么将根据先后顺序进行处理。
std::map<DelayedEntryTimeout, std::unique_ptr<QueuedTask>> delayed_queue_
RTC_GUARDED_BY(pending_lock_);
2.1.2.2. 私有成员类&结构
struct DelayedEntryTimeout {
int64_t next_fire_at_ms_{};
OederId order_{};
bool operator<(const DelayedEntryTimeout& o) const {
return std::tie(next_fire_at_ms_, order_) <
std::tie(o.next_fire_at_ms_, o.order_);
}
};
DelayedEntryTimeout
具有类似于时间戳的功能,next_fire_at_ms_
记录了任务执行的绝对时间,OrderId
则记录了任务对应的序号,重载了<
操作符用于比较两个
DelayedEntryTimeout
,该属性较小的任务会先被执行。
DelayedEntryTimeout
比较规则:先比较next_fire_at_ms_
,如果不等直接返回对应的bool
值;如果相等,则再次比较order
,返回比较结果对应的bool
值。
struct NextTask {
bool final_task_{false};
std::unique_ptr<QueuedTask> run_task_;
int64_t sleep_time_ms_{};
};
NextTask
用于表示下一个任务,前两个属性很容易理解,第三个属性是为了兼容DelayedTask
而设置的,具体的实现将会在GetNextTask
函数中介绍到。
2.1.2.3. 私有成员函数实现
2.1.2.3.1. GetNextTask
TaskQueu1Stdlib::NextTask TaskQueueStdlib::GetNextTask() {
NextTask result{};
// 获取当前的绝对时间(ms)
auto tick = rtc::TimeMillis();
rtc::CritScope lock(&pending_lock_);
// 判断是否线程需要退出
if (thread_should_quit_) {
result.final_task_ = true;
return result;
}
// 如果延迟任务队列非空,则首先从其中取出任务
if (delayed_queue_.size() > 0) {
// 获取延迟任务相关信息
auto delayed_entry = delayed_queue_.begin();
const auto& delay_info = delayed_entry->first;
auto& delay_run = delayed_entry->second;
// 判断是否应该执行延迟任务
if (tick >= delay_info.next_fire_at_ms_) {
// 如果即时任务队列非空,通过比较决定取出哪种任务
if (pending_queue_.size() > 0) {
auto& entry = pending_queue_.front();
auto& entry_order = entry.first;
auto& entry_run = entry.second;
// 如果即时任务序号较小,则直接返回该即时任务
if (entry_order < delay_info.order_) {
result.run_task_ = std::move(entry_run); //<--1
pending_queue_.pop();
return result;
}
}
// 如果即时任务队列为空,或者1处即时任务序号较大,则取出最靠前的延迟任务
result.run_task_ = std::move(delay_run); //<--2
delayed_queue_.erase(delayed_entry);
return result;
}
// !!如果运行到这里,此时result.task_run_为nullptr,表明没有任务需要处理
// 更新sleep_time_ms_
result.sleep_time_ms_ = delay_info.next_fire_at_ms_ - tick; //<--3
}
// 延迟任务队列为空,即时任务队列非空
if (pending_queue_.size() > 0) {
auto& entry = pending_queue_.front(); //<--4
result.run_task_ = std::move(entry.second);
pending_queue_.pop();
}
return result;
}
其中涉及到map
和queue
两个容器的相关函数,可以自行查阅。
由于NextTask
结构体内部的run_task_
为unique_ptr
,所以不能直接赋值而是使用std::move
转移所有权。
GetNextTask
函数体较长,逻辑略显复杂,而且非常重要,一定要理清楚如何从任务队列中取出任务。
代码块里做了四处标记,便于理解整个流程(此处分析认为thread_should_quit_
为true
):
if 延迟任务队列非空
if 需要执行下一个延迟任务
if 即时任务队列非空
if 即时任务序号较小
返回即时任务(1)
else
返回延迟任务(2)
else
返回延迟任务(2)
else
更新result.sleep_time_ms_(3)
if 即时任务队列非空
result.run_task_设置为即时任务(4)
返回result
这一部分可以概括为:
result.run_task_
为空,result.sleep_time_ms_
为0时:两个任务队列均为空result.run_task_
为空,result.sleep_time_ms_
不为0时:即时任务队列为空,延迟任务队列非空,但是没有可执行的延迟任务result.run_task_
不为空,result.sleep_time_ms_
为0时:即时任务队列非空,延迟任务队列为空result.run_task_
不为空,result.sleep_time_ms_
不为0时:即时任务队列非空,延迟任务队列非空,但是没有可执行的延迟任务
2.1.2.3.2 ThreadMain
// static
void TaskQueueStdlib::ThreadMain(void* context) {
TaskQueueStdlib* me = static_cast<TaskQueueStdlib*>(context);
CurrentTaskQueueSetter set_current(me);
me->ProcessTasks();
}
ThreadMain
是任务处理线程真正的入口函数,其首先将传入的参数强制转换成TaskQueueStdlib*
,然后将这个任务队列注册到当前的线程中,随后开始处理任务。
注意此函数是一个static
函数。
2.1.2.3.3. ProcessTasks
void TaskQueueStdlib::ProcessTasks() {
started_.Set();
while (true) {
auto task = GetNextTask();
if (task.final_task_)
break;
if (task.run_task_) {
// release()会解除智能指针对这个QueuedTask的占用,
// 并将该智能指针置空
QueuedTask* release_ptr = task.run_task_.release();
if (release_ptr->Run())
delete release_ptr;
// 取出下一个任务
continue;
}
// task.sleep_time_ms_为0时表示
if (0 == task.sleep_time_ms_)
flag_notify_.Wait(rtc::Event::kForever);
else
flag_notify_.Wait(task.sleep_time_ms_);
}
stopped_.Set();
}
ProcessTasks
函数与GetNextTask
函数协同工作。
由GetNextTask
函数部分的分析可知只要task.run_task_
为空,就说明即时任务队列为空且暂时没有需要执行的任务,但此时又面临着两种情况:
- 如果延迟任务队列为空,那么直接睡眠直到被唤起
- 如果延迟任务队列非空,那么将会睡眠指定时间
2.1.2.3.4. NotifyWake
void TaskQueueStdlib::NotifyWake() {
flag_notify_.Set();
}
任务队列中存放着待执行的任务:
- 对于即时任务,线程会忙于执行该任务而不会等待
flag_notify_
事件。 - 如果没有即时任务,但有一个延迟任务正在等待,那么线程将会等待
flag_notify_
事件,也就是ProcessTasks
中所提及到的flag_notify_.Wait(task.sleep_time_ms_);
- 如果即时任务队列和延迟任务队列都为空,那么线程将无限期等待
flag_notify_
事件,直到有一个信号显示有新的任务被添加(或者告诉线程需要终止)。
任何情况下,当一个新的上述请求被添加后,会发出flag_notify_
信号。如果此时线程正在等待,则会被立即唤醒并且重新评估下一步需要做什么。如果线程并没有在等待,那么线程将保持信号,在下一次试图等待flag_notify_
事件发生时被唤醒。
在发出flag_notify_
信号来唤醒可能正在睡眠的线程之前,需要确保有任务或相关请求添加到队列中,从而避免竞争情况:线程被通知唤醒但是发现没有任务需要执行,所以会再次等待信号,然而这样的信号将有可能不会再次出现。
2.1.3. 公有成员实现
2.1.3.1. 公有成员函数实现
2.1.3.1.1. TaskQueueStdlib
TaskQueueStdlib::TaskQueueStdlib(absl::string_view queue_name,
rtc::ThreadPriority priority)
: started_(/*manual_reset=*/false, /*initially_signaled=*/false),
stopped_(/*manual_reset=*/false, /*initially_signaled=*/false),
flag_notify_(/*manual_reset=*/false, /*initially_signaled=*/false),
thread_(&TaskQueueStdlib::ThreadMain, this, queue_name, priority) {
thread_.Start();
started_.Wait(rtc::Event::kForever);
}
创建一个任务处理线程,随后开始执行入口函数并挂起当前线程。
当任务处理线程准备就绪之后,会唤醒当前线程。
当前线程负责创建一个任务处理线程并且向其投递任务,任务处理线程负责处理任务队列内的任务。
2.1.3.1.2.Delete
void TaskQueueStdlib::Delete() {
RTC_DCHECK(!IsCurrent());
{
rtc::CritScope lock(&pending_lock_);
thread_should_quit_ = true;
}
NotifyWake();
stopped_.Wait(rtc::Event::kForever);
thread_.Stop();
delete this;
}
此函数用于销毁任务队列对象:
- 首先判断当前线程是不是任务处理线程,因为销毁操作不可以在任务处理线程中进行
- 随后标记任务处理线程需要退出并唤醒线程执行相关任务
- 然后当前线程等待任务处理线程退出后唤醒主线程
- 主线程回收任务处理线程,最后释放任务队列对象
2.1.3.1.3. PostTask
void TaskQueueStdlib::PostTask(std::unique_ptr<QueuedTask> task) {
{
rtc::CritScope lock(&pending_lock_);
// 序号自增赋值
OrderId order = thread_posting_order_++;
// 加入到即时任务队列中
pending_queue_.push(std::pair<OrderId, std::unique_ptr<QueuedTask>>(
order, std::move(task)));
}
// 唤醒任务处理线程处理任务
NotifyWake();
}
2.1.3.1.4. PostDelayedTask
void TaskQueueStdlib::PostDelayedTask(std::unique_ptr<QueuedTask> task,
uint32_t milliseconds) {
// 计算延迟任务的执行时间(绝对时间)
auto fire_at = rtc::TimeMillis() + milliseconds;
DelayedEntryTimeout delay;
// 设置延迟任务的执行时间
delay.next_fire_at_ms_ = fire_at;
{
rtc::CritScope lock(&pending_lock_);
// 序号自增赋值
delay.order_ = ++thread_posting_order_;
delayed_queue_[delay] = std::move(task);
}
// 唤醒任务处理线程处理任务
NotifyWake();
}
3. 总结
TaskQueueStdlib
涉及到任务队列的核心实现方式,尤其是GetNextTask
和ProcessTasks
两个函数,需要理清相关的逻辑,从而了解整个模块的流程。