版權聲明:原創文章,歡迎轉載,但請註明出處,謝謝。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=-1和milliseconds=正整數。
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是如何阻塞和喚醒線程的。如有錯誤敬請見諒,歡迎指出錯誤,我會虛心接受並加以改正。