WebRTC源碼剖析之事件-Event

版權聲明:原創文章,歡迎轉載,但請註明出處,謝謝。https://blog.csdn.net/qiuguolu1108/article/details/104324491

更多關於WebRTC源碼剖析的文章,請點擊《WebRTC源碼剖析》


Event類實現了事件的等待和觸發,通過接口Wait()函數可以實現線程的阻塞,而Set()函數可以激活阻塞的線程。

Event類在WebRTC的很多位置都有使用,理解了Event類才能更好的閱讀其他WebRTC源碼。

Event的使用示例

一直阻塞
#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事件的實現

發佈了5 篇原創文章 · 獲贊 1 · 訪問量 560
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章