WebRTC源码分析之事件-Event


Event类实现了事件的等待和触发,通过接口 Wait()函数可以实现线程的阻塞,而 Set()函数可以激活阻塞的线程。

Event类在WebRTC的很多位置都有使用,理解了Event类才能更好的阅读其他WebRTC源码。

Event使用示例
工程

示例工程:https://pan.baidu.com/s/1rbI2hwXpMA-Pb-i-zCdVWA 提取码:cenz

示例-一直阻塞
#include <iostream>
#include "event.h"
#include <signal.h>

using namespace std;
using namespace rtc;

Event e(false, false);

void sig_handler(int sig) 
{
    cout<<"signal is "<<sig<<endl;
    e.Set();    /*激活阻塞的线程*/
}

int main() 
{
    signal(SIGINT, sig_handler);   /*注册信号处理函数*/

    cout<<"waiting..."<<endl;

    e.Wait(-1);  /*将线程阻塞*/

    cout<<"coming..."<<endl;

    return 0;
}

在这里插入图片描述
在调用Wait()函数时将参数设置为-1,表示线程一直阻塞,直到调用Set()函数才能激活阻塞的线程。

示例-有超时的阻塞
#include <pthread.h>
#include <string.h>
#include <time.h>
#include <iostream>
#include "event.h"

using namespace std;
using namespace rtc;

Event e(false, false);

void * mythread(void * arg)
{
	time_t t = time(NULL);
	struct tm * st = localtime(&t);

    cout<<"before: "<<st->tm_hour<<":"<<st->tm_min<<":"<<st->tm_sec<<endl;

    cout<<"wait..."<<endl;
    e.Wait(5000);    /*将线程阻塞5s,5s后线程会自动被唤醒。*/

    t = time(NULL);
	st = localtime(&t);
    cout<<"after : "<<st->tm_hour<<":"<<st->tm_min<<":"<<st->tm_sec<<endl;
}

int main() 
{
	pthread_t th;
	
    /*创建线程*/
	int ret = pthread_create(&th,NULL,mythread,NULL);
	if(ret != 0)
	{
		cout<<strerror(ret)<<endl;
		exit(1);
	}

    /*回收线程*/
	pthread_join(th,NULL);

    return 0;
}

在这里插入图片描述
调用Wait()函数时,若参数不是-1,则线程阻塞指定的时间后,会自动的被唤醒。

示例-提前结束超时阻塞
#include <pthread.h>
#include <string.h>
#include <time.h>
#include <unistd.h>
#include <iostream>
#include "event.h"

using namespace std;
using namespace rtc;

Event e(false, false);

void * mythread(void * arg)
{
	time_t t = time(NULL);
	struct tm * st = localtime(&t);

    cout<<"before: "<<st->tm_hour<<":"<<st->tm_min<<":"<<st->tm_sec<<endl;

    cout<<"wait..."<<endl;
    e.Wait(20000);    /*20s后主动唤醒*/

    t = time(NULL);
	st = localtime(&t);
    cout<<"after : "<<st->tm_hour<<":"<<st->tm_min<<":"<<st->tm_sec<<endl;
}

int main() 
{
	pthread_t th;

	int ret = pthread_create(&th,NULL,mythread,NULL);
	if(ret != 0)
	{
		cout<<strerror(ret)<<endl;
		exit(1);
	}

    /*5s后主动唤醒被阻塞的线程*/
	sleep(5);
	cout<<"WakeUp..."<<endl;
	e.Set();

	pthread_join(th,NULL);

    return 0;
}

在这里插入图片描述

使用具有超时的Wait()函数,可以主动调用Set()提前将线程唤醒。

Event源码分析
实现原理

在linux平台上,Event通过系统提供的互斥锁条件变量实现的。

Event的声明

Event类所在文件的位置:src\rtc_base\event.h event.cc

class Event 
{
 public:
  static const int kForever = -1;   /*表示没有超时,一直阻塞。*/

  Event();
  Event(bool manual_reset, bool initially_signaled);
  Event(const Event&) = delete;       
  Event& operator=(const Event&) = delete;
  ~Event();

  void Set();
  void Reset();
 
  bool Wait(int milliseconds);

 private:
  pthread_mutex_t event_mutex_;     /*互斥锁*/
  pthread_cond_t event_cond_;       /*条件变量*/
    
  const bool is_manual_reset_;      /*手动重置*/
  bool event_status_;               /*事件状态*/
};

Event类的主要通过互斥锁(event_mutex_)条件变量(event_cond_)实现线程的阻塞和激活。

event_status_就是条件变量等待的条件,其值为true时表示条件成立,线程被唤醒。

构造器和析构器
Event::Event() : Event(false, false) {}

Event::Event(bool manual_reset, bool initially_signaled)
    : is_manual_reset_(manual_reset), event_status_(initially_signaled) 
{
  /*初始化互斥锁*/
  RTC_CHECK(pthread_mutex_init(&event_mutex_, nullptr) == 0);  

  /*定义条件变量的属性*/
  pthread_condattr_t cond_attr;

  /*初始化条件变量的属性*/
  RTC_CHECK(pthread_condattr_init(&cond_attr) == 0);

  /*初始化条件变量*/
  RTC_CHECK(pthread_cond_init(&event_cond_, &cond_attr) == 0);

  /*销毁条件变量属性*/
  pthread_condattr_destroy(&cond_attr);
}

/*在析构时,需要析构掉互斥锁和条件变量。*/
Event::~Event() 
{
  /*销毁互斥锁*/
  pthread_mutex_destroy(&event_mutex_);
    
  /*销毁条件变量*/
  pthread_cond_destroy(&event_cond_);
}

在构造器中主要是初始化互斥锁条件变量,在析构器中主要是销毁这个两者。

时间转换函数
timespec GetTimespec(const int milliseconds_from_now) 
{
  timespec ts;

  timeval tv;
  gettimeofday(&tv, nullptr);      /*从系统获取现在时间*/
    
  ts.tv_sec = tv.tv_sec;           /*秒*/
  ts.tv_nsec = tv.tv_usec * 1000;  /*纳秒*/
    
  /*计算milliseconds_from_now毫秒后的时间是多少*/
  ts.tv_sec += (milliseconds_from_now / 1000);
  ts.tv_nsec += (milliseconds_from_now % 1000) * 1000000;  /*不足1毫秒的转成纳秒*/

  /*如果纳秒值超过了1秒,将纳秒转成秒。*/
  if (ts.tv_nsec >= 1000000000) 
  {
    ts.tv_sec++;
    ts.tv_nsec -= 1000000000;
  }

  return ts;
}

GetTimespec()函数用于计算milliseconds_from_now毫秒后的时间是多少。这个函数主要用于计算事件被激活的时间的多少。

阻塞事件
bool Event::Wait(const int milliseconds) 
{
  const timespec ts = GetTimespec(milliseconds == kForever ? 3000 : milliseconds);

  /*线程获取锁以后,才能往下执行。*/
  pthread_mutex_lock(&event_mutex_);

  int error = 0;

  while (!event_status_ && error == 0) 
  {
	/*条件不满足时,被阻塞在条件变量上,同时释放刚才获取的锁。*/
    error = pthread_cond_timedwait(&event_cond_, &event_mutex_, &ts);
  }

  if (milliseconds == kForever && error == ETIMEDOUT) 
  {
    error = 0;
    while (!event_status_ && error == 0)  
	{
      /*在没有被唤醒时,线程会一直阻塞在这里。*/
      error = pthread_cond_wait(&event_cond_, &event_mutex_);
    }
  }

  /*is_manual_reset_为false,表示自动重置。*/
  if (error == 0 && !is_manual_reset_)   
    event_status_ = false;

  pthread_mutex_unlock(&event_mutex_);

  return (error == 0);
}

根据Wait()函数参数的不同,分成两种情况:milliseconds=-1milliseconds=正整数

milliseconds=-1时,表示线程会一直阻塞,直到调用Set()函数才能将线程唤醒。执行过程分析如下:因为milliseconds=-1,由milliseconds == kForever ? 3000 : milliseconds知结果是3000毫秒,执行到pthread_cond_timedwait()函数时,线程先在这个函数上阻塞3秒钟。阻塞3秒后,从这个函数退出,同时返回值是ETIMEOUT,从而跳出当前所在的while循环。因为milliseconds=kForever并且error=ETIMEOUT,所以线程再次被阻塞在pthread_cond_wait()函数上。

当线程被阻塞在pthread_cond_timedwait()函数时,调用了Set()函数,event_status_被设置为true,线程被唤醒,同时返回值是0,则跳出当前while循环。因为error的值是0,所以不会进入下面while循环,结束Wait()函数。

当线程被阻塞在pthread_cond_wait()函数时,调用了Set()函数,event_status_被设置为true,线程被唤醒,跳出当前while循环,结束Wait()函数。

唤醒事件
void Event::Set() 
{
  /*上锁对event_status_修改*/
  pthread_mutex_lock(&event_mutex_);
    
  event_status_ = true;  /*修改条件*/
    
  pthread_cond_broadcast(&event_cond_);  /*解锁所有被挂起的线程*/
    
  pthread_mutex_unlock(&event_mutex_);
}

Set()函数将唤醒阻塞的线程。pthread_cond_broadcast()才真正的激活阻塞的线程,修改event_status_为true,是避免线程再次被阻塞,修改这个值,说明条件满足了,线程不需要再被阻塞了。

重置事件
void Event::Reset() 
{
  pthread_mutex_lock(&event_mutex_);
  event_status_ = false;
  pthread_mutex_unlock(&event_mutex_);
}

调用Reset()函数,将event_status_手动置为false。

在手动重置事件中,使用Event的方式是:Wait() —— Set() —— Reset() —— Wait() —— Set() —— Reset() …

在自动重置事件中,使用使用Event的方式是:Wait() —— Set() —— Wait() —— Set() …省去了调用Reset()

在初始化Event类对象时,若通过传参的方式将event_status_置为false,则在Wait()函数最后会主动的把event_status_置为false,避免了手动调用Reset()

在无参构造Event对象时,会默认的把event_status_置为false。

小结

本文通过示例和阅读源码的方式,分析了Event类,弄清了WebRTC是如何阻塞和唤醒线程的。

参考:webRTC base模块Event事件的实现

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值