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=-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是如何阻塞和唤醒线程的。