WebRTC源码分析-TaskQueue(任务队列)-TaskQueueBase

1. 前言

任务队列TaskQueue是WebRTC中非常核心的一部分,其主要功能是将任务投递到某一个线程执行。TaskQueue是WebRTC中进程交互很重要的方式。
本文主要分析TaskQueue中最重要的基类TaskQueueBase
TaskQueue机制中涉及的其他类后续会继续补充。
WebRTC版本:M84

2. 正文

2.0. 预说明:线程局部存储

任务队列不可避免地涉及到多线程的知识,此处仅简单介绍一下TaskQueueBase部分涉及到的相关内容以及函数。

2.0.1. 线程局部存储概念

线程局部存储(TLS,Thread Local Storage)是线程私有的全局变量。普通的全局变量是多个线程共享的,一个线程对其修改,所有线程均可见。而线程局部存储是线程私有的,每个线程都有自己的一个副本,某个线程对其所做修改只会修改自己的副本,不会影响到其他线程的副本。

2.0.2. pthread_key_t

ABSL_CONST_INIT pthread_key_t g_queue_ptr_tls = 0;

上述声明中pthread_key_t为前一小节提到的线程局部存储类型

2.0.3. Tls相关函数说明

int pthread_key_create(pthread_key_t *key, void (*destructor)(void*));
int pthread_setspecific(pthread_key_t key, const void *value);
void* pthread_getspecific(pthread_key_t key);
  • 调用pthread_key_create函数可以创建pthread_key_t变量。该函数需要提供两个参数,第一个参数是需要创建的pthread_key_t变量,第二个参数是一个释放函数,在线程释放其Tls的时候被调用。如果函数指针被设置成nullptr,那么系统将调用默认释放函数。该函数成功创建变量时返回0,其他任何返回值均代表出现异常。
  • 当线程中需要存储值的时候,可以调用 pthread_setspcific函数。该函数需要提供两个参数,第一个参数为前面声明的pthread_key_t变量,第二个为void*变量,也就意味着可以存储任何类型的值。
  • 如果需要取出所存储的值,调用pthread_getspecific函数。该函数的参数为前面提到的pthread_key_t变量,该函数返回void *类型的值,如需使用,则要进行强制类型转换。

2.0.4. InitializeTls

void InitializeTls(){
    RTC_CHECK(pthread_key_create(&g_queue_ptr_tls, nullptr)==0);
}

RTC_CHECK()为WebRTC中的断言宏,当其内部参数为false时,抛出异常,直接中止当前进程。

2.0.5. GetQueuePtrTls

pthread_key_t GetQueuePtrTls(){
    static pthread_once_t init_once = PTHREAD_ONCE_INIT;
    RTC_CHECK(pthread_once(&init_once, &InitializeTls) == 0);
    return g_queue_ptr_tls; 
}

调用pthread_once()函数,传入初值为PTHREAD_ONCE_INITinit_once变量,结合RTC_CHECK()断言保证InitializeTls()函数仅执行一次。

2.0.6. 说明

  • 上述所有函数为全局函数 不属于任何一个对象
  • 上述所有函数只在WEBRTC_POSIX宏定义的前提下被定义,否则不被定义
  • 定义这些函数是为了供TaskQueueBase类使用

2.1. QueuedTask

Path:
api\task_queue\queued_task.h

2.1.1. 类声明

namespace webrtc{
class QueuedTask{
  public:
    virtual ~QueuedTask() = default;
    virtual bool Run() = 0;
};// class QueuedTask
} // namespace webrtc

QueueTask是一个抽象类,为需要异步执行的任务提供基本接口。
这个接口由一个单一的函数Run()组成,它会在目标队列上执行,Run()具体实现的功能由其子类决定。

2.2. TaskQueueBase

Path:
api\task_queue\task_queue_base.h
api\task_queue\task_queue_base.cc

2.2.1. 类声明

namespace webrtc{
class RTC_LOCKABLE RTC_EXPORT TaskQueueBase {
  public:
    virtual void Delete() = 0;
    virtual void PostTask(std::unique_ptr<QueuedTask> task) = 0;
    virtual void PostDelayedTask(std::unique_ptr<QueuedTask> task,
                                 uint32_t milliseconds) = 0;
    static TaskQueueBase* Current();
    bool IsCurrent() const { return Current == this; }
  protected:
    class CurrentTaskQueueSetter{
      public:
        explicit CurrentTaskQueueSetter(TaskQueueBase* task_queue);
        CurrentTaskQueueSetter(const CurrentTaskQueueSetter&) = delete;
        CurrentTaskQueueSetter& operator=(const CurrentTaskQueueSetter&) = delete;
        ~CurrentTaskQueueSetter();
      private:
        TaskQueueBase* const previous_;
    };// class CurrentTaskQueueSetter
    virtual ~TaskQueueBase() = default;
    
};// class TaskQueueBase
} // namespace webrtc

2.2.2. 函数实现及相关说明

2.2.2.1. Delete

virtual void Delete() = 0;

纯虚函数,基类中无需实现。
调用此函数开始销毁任务队列,此函数在返回时需要确保没有任务正在运行,也没有新的任务能够在任务队列中启动。
同时此函数负责释放对象,释放动作可以在Delete期间同步进行,也可以在Delete之后异步进行。
销毁某个任务队列对不在此任务队列中的任务不产生任何影响,这些任务也不会因为其他的任务队列销毁而调用任何函数。
在任务队列上执行的任务不可以调用Delete,但是可以调用其他的函数,比如PostTask

2.2.2.2. PostTask

virtual void PostTask(std::unique_ptr<QueuedTask> task) = 0;

纯虚函数,基类中无需实现。
此函数用于安排一个即时任务的处理,这些任务按照先进先出的顺序执行(所以称为任务队列)。
如果task->Run()的返回值是true,代表任务成功执行,任务会在下一个QueuedTask开始执行之前从任务队列中被移除。

2.2.2.3. PostDelayTask

virtual void PostDelayedTask(std::unique_ptr<QueuedTask> task, 
                                           uint32_t milliseconds) = 0;

纯虚函数,基类中无需实现。
此函数用于安排一个延迟任务的处理,处理会在调用PostDelayedTask函数后的milliseconds毫秒后执行。
关于延迟时间的精度可以称作“尽力而为”,在某些场景下,定时可能会有一些毫秒级的误差。

2.2.2.4. CurrentIsCurrent

需要说明的是,从此处开始,相关的的定义会根据所定义的宏而不同,代码块中会给出相关说明。
变量及函数声明:

static TaskQueueBase* Current();
bool IsCurrent() const { return Current() == this; }

//if defined ABSL_HAVE_THREAD_LOCAL
ABSL_CONST_INIT thread_local TaskQueueBase* current = nullptr;

Current()函数定义:

// defined ABSL_HAVE_THREAD_LOCAL
TaskQueueBase* TaskQueueBase::Current() {
  return current;
}
// defined WEBRTC_POSIX
TaskQueueBase* TaskQueueBase::Current() {
  return static_cast<TaskQueueBase*>(pthread_getspecific(GetQueuePtrTls()));
}

Current()函数返回当前线程保存的任务队列,返回值为一个static变量。
IsCurrent()函数则用于判断当前线程保存的任务队列是不是对象本身。

2.2.3. 说明

  • TaskQueueBase是抽象基类,只用于提供接口,不可以被实例化
  • 基于上一条说明,TaskQueueBase的析构函数被声明为虚函数
  • 考虑到文章结构,TaskQueueBase的成员类会在下一节介绍

2.3. TaskQueue::CurrentTaskQueueSetter

Path:
api\task_queue\task_queue_base.h
api\task_queue\task_queue_base.cc

2.3.1. 类声明

namespace webrtc {
class RTC_LOCKABLE RTC_EXPORT TaskQueueBase {
  //...
protected:
  class RTC_EXPORT CurrentTaskQueueSetter {
    public:
    explicit CurrentTaskQueueSetter(TaskQueueBase* task_queue);
    CurrentTaskQueueSetter(const CurrentTaskQueueSetter&) = delete;
    CurrentTaskQueueSetter& operator=(const CurrentTaskQueueSetter&) = delete;
    ~CurrentTaskQueueSetter();

    private:
    TaskQueueBase* const previous_;
  };// class CurrentTaskQueueSetter
    //...
};// class TaskQueueBase
}// namespace webrtc

2.3.2.函数实现及相关说明

2.3.2.1. CurrentTaskQueueSetter

// defined ABSL_HAVE_THREAD_LOCAL
TaskQueueBase::CurrentTaskQueueSetter::CurrentTaskQueueSetter(
    TaskQueueBase* task_queue)
    : previous_(current) {
  current = task_queue;
}

// defined WEBRTC_POSIX
TaskQueueBase::CurrentTaskQueueSetter::CurrentTaskQueueSetter(
    TaskQueueBase* task_queue)
    : previous_(TaskQueueBase::Current()) {
  pthread_setspecific(GetQueuePtrTls(), task_queue);
}

构造函数主要的任务分为两部分:

  • 使用previous_暂存当前线程的任务队列
  • task_queue存放在当前线程的Tls

2.3.2.2. ~CurrentTaskQueueSetter

// defined ABSL_HAVE_THREAD_LOCAL
TaskQueueBase::CurrentTaskQueueSetter::~CurrentTaskQueueSetter() {
  current = previous_;
}

// defined WEBRTC_POSIX
TaskQueueBase::CurrentTaskQueueSetter::~CurrentTaskQueueSetter() {
  pthread_setspecific(GetQueuePtrTls(), previous_);
}

构造函数的任务就是将之前暂存在previous_的任务队列取出,重新放回到当前线程的Tls当中。

2.3.3.说明

CurrentTaskQueueSetter类只在构造和析构时执行任务:

  • 构造时,用传入构造函数的任务队列更新当前线程存放的任务队列,并将更新前的任务队列暂存
  • 析构时,用构造时暂存的任务队列更新当前线程存放的任务队列

3. 总结

TaskQueueBase类作为任务队列机制的核心基类,在后续分析中经常会涉及到,了解其实现方法有助于了解整个任务队列的运行机制。
关于任务队列的其他类,后续会在其他文章中进行分析。

你可能感兴趣的:(WebRTC源码分析,音视频,webrtc)