WebRTC源码分析之任务队列-TaskQueue

文章目录

        • TaskQueue使用示例
          • 工程
          • 示例
        • TaskQueue源码分析
          • 类关系图
          • TaskQueueBase类
            • 声明
            • CurrentTaskQueueSetter类
          • TaskQueueStdlib类
            • 数据成员
            • 任务队列的创建
            • 销毁任务队列
            • 唤醒任务处理线程
            • 投递任务
            • 任务处理线程处理任务
          • QueuedTask类
          • TaskQueue类
            • 声明
          • 工厂模式创建任务队列对象
            • TaskQueueDeleter类
            • TaskQueueFactory类
            • TaskQueueStdlibFactory类
            • CreateTaskQueueStdlibFactory函数创建工厂对象
        • 小结

在WebRTC中,会单独创建线程用于处理即时任务和延迟任务。其他线程可以将待做的事包装成任务投递到该线程去,由该线程执行。这个线程只做一件事,就是循环的处理任务。

TaskQueue使用示例

工程

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

rtc_executable("webrtc_learn"){
    testonly = true
  sources = [
    "webrtclearn/main.cc"         
  ]
  deps = [
    "../rtc_base:rtc_base",
    "../rtc_base:rtc_task_queue_stdlib", 
    "../api/task_queue:task_queue"
  ]
}
示例
#include 
#include 
#include "rtc_base/task_queue.h"
#include "rtc_base/task_queue_stdlib.h"
#include "rtc_base/platform_thread.h"
#include "api/task_queue/queued_task.h"
#include "api/task_queue/task_queue_base.h"
#include "api/task_queue/task_queue_factory.h"

using namespace std;
using namespace webrtc;
using  namespace rtc;

/*定义即时任务*/
class instant_task :public QueuedTask
{
public:
    instant_task(string thread_name)
        :thread_name_(thread_name)
    {}

    bool Run()  override
    {
        cout << thread_name_ << " post a task..." << endl;

        return true;
    }

private:
    string thread_name_;
};

/*定义延迟任务*/
class delay_task : public QueuedTask 
{
 public:
    delay_task(string thread_name)
      : thread_name_(thread_name)
    {}

  bool Run() override
  {
    cout << thread_name_ << " post a task..." << endl;

    return true;
  }

 private:
  string thread_name_;
};

void other_thread(void* arg)
{
    TaskQueue* tq = (TaskQueue*)arg;

    unique_ptr<QueuedTask> instantTask(new instant_task("child_thread"));
    unique_ptr<QueuedTask> delayedTask(new delay_task("child_thread"));

    /*延迟任务3秒后执行*/
    tq->PostDelayedTask(std::move(delayedTask), 3 * 1000);

    /*这个任务是最先执行*/
    tq->PostTask(std::move(instantTask));

    /*睡眠5秒等待任务被执行*/
    Sleep(5000);
}

int main()
{
    /*定义一个工厂*/
    unique_ptr<TaskQueueFactory> tqFactory = CreateTaskQueueStdlibFactory();

    /*用工厂创建任务队列*/
    unique_ptr<TaskQueueBase, TaskQueueDeleter> tqb = tqFactory->CreateTaskQueue("task queue", TaskQueueFactory::Priority::NORMAL);
    
    /*托管任务队列*/
    TaskQueue tq(std::move(tqb));
      
    /*开启一个子线程*/
    PlatformThread other_th(other_thread, (void*)&tq, "child_thread");
    other_th.Start();

    /*睡眠1秒后投递任务*/
    Sleep(1000);

    /*投递即时任务*/
    unique_ptr<QueuedTask> task(new instant_task("main_thread"));
    tq.PostTask(std::move(task));
    
    /*回收线程*/
    other_th.Stop();

    return 0;
}

在这里插入图片描述
WebRTC源码分析之任务队列-TaskQueue_第1张图片

在创建任务队列时,会创建一个线程,这个线程只用于任务的处理,其他线程可以向其投递任务。在主线程中又创建了一个子线程,并将任务队列传入,在子线程也可以向其投递任务。

TaskQueue源码分析

类关系图

WebRTC源码分析之任务队列-TaskQueue_第2张图片

TaskQueueBase类

TaskQueueBase类所在文件的位置:src\api\task_queue\task_queue_base.h task_queue_base.cc

声明
class RTC_LOCKABLE 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; }
    
    /*需要将析构器置为虚函数,为了完整的析构。*/
    virtual ~TaskQueueBase() = default;
}

TaskQueueBase类是虚基类,不能实例化,只能用于提供接口。在释放任务队列的时候,不能直接将其删除,需要等待任务队列处理完正在处理的任务后,才能释放任务队列。

CurrentTaskQueueSetter类
class CurrentTaskQueueSetter 
{
 public:
  explicit CurrentTaskQueueSetter(TaskQueueBase* task_queue);
  CurrentTaskQueueSetter(const CurrentTaskQueueSetter&) = delete;
  CurrentTaskQueueSetter& operator=(const CurrentTaskQueueSetter&) = delete;
  ~CurrentTaskQueueSetter();

 private:
  TaskQueueBase* const previous_; /*保存的是上一个任务队列*/
};

用于把任务队列绑定到当前线程上

/*创建线程局部存储*/
ABSL_CONST_INIT pthread_key_t g_queue_ptr_tls = 0;

/*全局的函数*/
void InitializeTls() 
{
  /*创建TLS*/
  RTC_CHECK(pthread_key_create(&g_queue_ptr_tls, nullptr) == 0);
}

/*全局的函数*/
pthread_key_t GetQueuePtrTls() 
{
  /*保证InitializeTls()只运行一次*/
  static pthread_once_t init_once = PTHREAD_ONCE_INIT;
    
  RTC_CHECK(pthread_once(&init_once, &InitializeTls) == 0);

  return g_queue_ptr_tls;
}

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

线程局部存储的创建通过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)函数。

TaskQueueBase::CurrentTaskQueueSetter::CurrentTaskQueueSetter(TaskQueueBase* task_queue)
    : previous_(TaskQueueBase::Current())   /*将当前任务队列保存下来*/
{
  /*任务队列保存在当前线程的TLS中*/
  pthread_setspecific(GetQueuePtrTls(), task_queue);
}

在生成CurrentTaskQueueSetter对象时,会把指定的任务队列保存在当前线程内。

TaskQueueBase::CurrentTaskQueueSetter::~CurrentTaskQueueSetter() 
{
  /*将之前的任务队列保存到线程内*/
  pthread_setspecific(GetQueuePtrTls(), previous_);
}

CurrentTaskQueueSetter对象被释放时,需要将之前的任务队列重新保存到线程内。

TaskQueueBase* TaskQueueBase::Current() 
{
  return static_cast<TaskQueueBase*>(pthread_getspecific(GetQueuePtrTls()));
}

返回当前线程保存的任务队列

TaskQueueStdlib类

TaskQueueStdlib类所在文件的位置:src\rtc_base\task_queue_stdlib.cc

数据成员
/*用于将主线程阻塞或唤醒*/
rtc::Event started_;
rtc::Event stopped_;

/*用于将任务处理线程阻塞或唤醒*/
rtc::Event flag_notify_;

/*任务处理线程的操作句柄*/
rtc::PlatformThread thread_;

/*标识任务处理线程是否退出*/
bool thread_should_quit_ RTC_GUARDED_BY(pending_lock_){false};

/*任务序号种子*/
using OrderId = uint64_t;
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_);

RTC_GUARDED_BY(pending_lock_)是宏函数,使用clang编译器时被展开为__attribute__((guarded_by(pending_lock_)))。这是给编译器使用的,让编译器检查在使用前面的变量时需要获取pending_lock_锁。

即时任务存放在队列中,按照FIFO执行任务。延迟任务存放在map中,按照延迟时间排序。

任务队列的创建

先作一个约定,创建任务队列对象的线程叫作主线程,由主线程创建的用于处理任务的线程叫作任务处理线程

TaskQueueStdlib::TaskQueueStdlib(absl::string_view queue_name,rtc::ThreadPriority priority)
    : started_(false,false),        /*初始化事件*/
      stopped_(false,false),
      flag_notify_(false,false),    /*创建任务处理线程对象*/
      thread_(&TaskQueueStdlib::ThreadMain, this, queue_name, priority)
{
  /*创建任务处理线程,并开始执行入口函数。*/
  thread_.Start();  
          
  /*主线程被挂起*/
  started_.Wait(rtc::Event::kForever);  
}

在任务处理线程未就绪之前,主线程会被挂起,任务处理线程就绪以后会再次把主线程唤醒。

void TaskQueueStdlib::ThreadMain(void* context)   
{
  /*将参数强转成TaskQueueStdlib*  */
  TaskQueueStdlib* me = static_cast<TaskQueueStdlib*>(context);

  /*将任务队列注册到当前线程中*/
  CurrentTaskQueueSetter set_current(me);

  /*开始处理任务*/
  me->ProcessTasks();
}

这是任务处理线程真正的入口函数,通过传参的方式获取任务队列对象,将任务队列保存到任务处理线程中,准备工作做好后,开始处理其他线程投递的任务。

销毁任务队列
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;
}

要销毁任务队列对象时,需要主动调用Delete()函数,等待任务处理线程处理完正在处理的任务,任务处理线程退出后,需要回收线程资源。

唤醒任务处理线程
void TaskQueueStdlib::NotifyWake() 
{
    /*唤醒任务处理线程*/
	flag_notify_.Set();
}

任务处理线程将所有任务处理完毕后,没有事做了,会进入睡眠状态。当有新的任务到达以后,需要唤醒线程去处理任务。

投递任务
/*投递即时任务*/
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();
}

thread_posting_order_pending_queue_存在着多线程访问,需要上锁。

投递了即时任务以后需要激活任务处理线程去处理任务。

/*投递延迟任务*/
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();
}

投递延迟任务,任务会在指定的时间被执行。

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_{};               /*任务处理线程休眠时间*/
};

对任务及其必要信息进行封装

/*获取下一个任务*/
TaskQueueStdlib::NextTask TaskQueueStdlib::GetNextTask() 
{
  /*初始化为nullptr*/
  NextTask result{};    

  /*此刻的时间,用于检测延迟任务是否可以执行。*/
  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;

	/*获取任务对象 queuedTask*/
    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);
            
          /*从队列中弹出*/
          pending_queue_.pop();    
            
          /*执行即时任务*/
          return result;
        }
      }

	  /*延迟任务的序号小*/
      result.run_task_ = std::move(delay_run);
        
      /*从map中删除*/
      delayed_queue_.erase(delayed_entry);  
	   
      /*执行延迟任务*/
      return result;
    }

	/*注意:执行到这里时result.run_task是nullptr*/
	/*延迟任务没有到执行时间,计算下次延迟任务的执行时间。*/
    result.sleep_time_ms_ = delay_info.next_fire_at_ms_ - tick;
  }

  /*如果没有延迟任务可执行,则在即时任务中取出一个任务。*/
  if (pending_queue_.size() > 0) 
  {
    auto& entry = pending_queue_.front();
    result.run_task_ = std::move(entry.second);
    pending_queue_.pop();
  }

  /*
   * result.run_task为nullptr,result.sleep_time_ms_不为0时:
   *     没有即时任务也没有要执行的延迟任务
   *
   * result.run_task不为nullptr,result.sleep_time_ms_为0时:
   *     有即时任务但没有延迟任务
   *
   * result.run_task不为nullptr,result.sleep_time_ms_不为0时:
   *     有即时任务但没有可执行的延迟任务
   *
   * result.run_task为nullptr,result.sleep_time_ms_为0时:
   *     任何任务都没有
   */
  return result;
}

在即时任务和延迟任务都可以执行时,需要按照任务的序号执行任务,保证任务总体上按照FIFO执行。

void TaskQueueStdlib::ProcessTasks() 
{
  /*任务处理线程就绪,激活主线程。*/
  started_.Set();   

  while (true) 
  {
	/*从任务表(队列、map)中获取了一个待执行的任务。*/
    auto task = GetNextTask();

	/*首先判断,是否要结束本任务队列线程*/
    if (task.final_task_)
      break;    /*要销毁任务处理线程了,跳出循环。*/

	/*判断是否有任务需要处理*/
    if (task.run_task_) 
	{
      QueuedTask* release_ptr = task.run_task_.release();  
      
      /*执行任务*/
      if (release_ptr->Run())   
        delete release_ptr;    

      /*获取下一个任务,继续执行。*/
      continue;
    }

    if (0 == task.sleep_time_ms_) 
      /*说明没有延迟任务也没有即时任务,则一直睡眠,直到被激活。*/
      flag_notify_.Wait(rtc::Event::kForever);
    else
      /*有待执行的延迟任务,则线程睡眠指定的时间。*/
      flag_notify_.Wait(task.sleep_time_ms_);   
  }

  /*通知主线程,本线程准备退出了。*/
  stopped_.Set();
}

task.run_task_不为nullptr,说明有任务待执行,可能是即时任务也可能是延迟任务。当task.sleep_time_ms_为0表示没有任何任务需要执行,若不为0表示有延迟任务待执行,并且延迟任务在task.sleep_time_ms_毫秒后执行,没有任务时,任务处理线程进入睡眠。

QueuedTask类

QueuedTask类所在文件的位置:src\api\task_queue\queued_task.h

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

所有需要执行的任务都需要继承此类,并覆写Run()函数。

TaskQueue类

TaskQueue类所在文件的位置:src\rtc_base\task_queue.h task_queue.cc

使用代理模式对任务队列进行了简单的包装

声明
class RTC_LOCKABLE RTC_EXPORT TaskQueue 
{
 public:
  using Priority = ::webrtc::TaskQueueFactory::Priority;  

  explicit TaskQueue(std::unique_ptr<webrtc::TaskQueueBase,webrtc::TaskQueueDeleter> task_queue);
    
  ~TaskQueue();

  bool IsCurrent() const;

  webrtc::TaskQueueBase* Get() { return impl_; }

  void PostTask(std::unique_ptr<webrtc::QueuedTask> task);

  void PostDelayedTask(std::unique_ptr<webrtc::QueuedTask> task,uint32_t milliseconds);

 private:
  webrtc::TaskQueueBase* const impl_;  /*保存着托管的任务队列对象*/

  RTC_DISALLOW_COPY_AND_ASSIGN(TaskQueue);
};

TaskQueue类主要是对任务队列对象进行托管,任务队列对象在释放时需要主动调用Delete()函数。

TaskQueue::TaskQueue(std::unique_ptr<webrtc::TaskQueueBase, webrtc::TaskQueueDeleter> task_queue)
    : impl_(task_queue.release()) {}

TaskQueue::~TaskQueue() 
{
  /*主动调用Delete()函数释放对象*/
  impl_->Delete();
}

bool TaskQueue::IsCurrent() const 
{
  return impl_->IsCurrent();
}

void TaskQueue::PostTask(std::unique_ptr<webrtc::QueuedTask> task) 
{
  return impl_->PostTask(std::move(task));
}

void TaskQueue::PostDelayedTask(std::unique_ptr<webrtc::QueuedTask> task,uint32_t milliseconds) 
{
  return impl_->PostDelayedTask(std::move(task), milliseconds);
}

这些函数都是进行了一层简单的包装

工厂模式创建任务队列对象
TaskQueueDeleter类

TaskQueueDeleter类所在文件的位置:src\api\task_queue\task_queue_base.h

struct TaskQueueDeleter   
{
  /*仿函数,用于释放TaskQueueBase。*/
  void operator()(TaskQueueBase* task_queue) const 
  { 
      task_queue->Delete(); 
  }
};

unique_ptr指针中用于释放TaskQueueBase对象,TaskQueueBase对象的释放需要主动调用Delete()安全的释放。

unique_ptr指针在释放托管的对象时,默认使用delete释放对象,也可以使用用户指定的释放器。示例如下:

#include 
#include 

using namespace std;

class A
{
public:
	A(){ cout <<this<< ":  constructor" << endl;}

	~A(){ cout << this << ":  deconstructor" << endl;}
};

class Deleter
{
public:

	void operator()(A* p)
	{
		cout << p << ":  delete A ..." << endl;

		delete p;  /*调用析构器*/
	}
};

int main()
{
    /*使用用户提供的释放器*/
	unique_ptr<A, Deleter> pa(new A());
	   
	return 0;
}

在这里插入图片描述

TaskQueueFactory类

TaskQueueFactory类所在文件的位置:src\api\task_queue\task_queue_factory.h

class TaskQueueFactory 
{
 public:

  enum class Priority { NORMAL = 0, HIGH, LOW };

  virtual ~TaskQueueFactory() = default;
  virtual std::unique_ptr<TaskQueueBase, TaskQueueDeleter> CreateTaskQueue(absl::string_view name,Priority priority) const = 0;
};

创建任务队列的工厂类

TaskQueueStdlibFactory类

TaskQueueStdlibFactory类所在文件的位置:src\rtc_base\task_queue_stdlib.cc

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

任务队列的优先级转换成线程的优先级

class TaskQueueStdlibFactory final : public TaskQueueFactory 
{
 public:
	std::unique_ptr<TaskQueueBase, TaskQueueDeleter> CreateTaskQueue(absl::string_view name,Priority priority) const override 
	{
		return std::unique_ptr<TaskQueueBase, TaskQueueDeleter>(new TaskQueueStdlib(name, TaskQueuePriorityToThreadPriority(priority)));
	}
};

调用CreateTaskQueue()函数创建TaskQueueStdlib对象时,需要指定释放器,不能单纯的调用delete释放TaskQueueStdlib对象。

CreateTaskQueueStdlibFactory函数创建工厂对象
std::unique_ptr<TaskQueueFactory> CreateTaskQueueStdlibFactory() 
{
  return absl::make_unique<TaskQueueStdlibFactory>();
}

CreateTaskQueueStdlibFactory()是全局函数用于创建TaskQueueStdlibFactory工厂对象,TaskQueueStdlibFactory工厂对象可以用于创建TaskQueueStdlib对象。

小结

本文仅介绍了TaskQueueBase类的一个子类TaskQueueStdlibTaskQueueLibeventTaskQueueWin等也继承自TaskQueueBase类,暂时还不清楚这些类之间的本质区别,以后会逐个分析这些类,但掌握TaskQueueStdlib的思想对于分析其他类会十分有帮助。

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