admin管理员组文章数量:1530041
文章目录
- CriticalSection使用示例
- 工程
- 不加锁
- 加锁-使用pthread库中的互斥锁
- 加锁-使用CriticalSection
- 加锁-使用CritScope
- 加锁-使用GlobalLock
- CriticalSection源码分析
- CriticalSection类
- 类的声明
- 构造器和析构器
- CurrentThreadIsOwner函数
- Enter函数
- TryEnter函数
- Leave函数
- CritScope类
- 类的声明
- 构造器和析构器
- TryCritScope类
- GlobalLockPod类
- GlobalLock类
- GlobalLockScope类
- 小结
多线程在使用临界区时需要加锁,WebRTC封装了跨平台的锁 CriticalSection、 GlobalLock,为了简化锁的使用,提供了
区域锁
CritScope、
TryCritScope、
GlobalLockScope。
CriticalSection使用示例
我们用两个线程对同一个全局变量各加1000000次。不同的编程方式如下:
工程
示例工程:https://pan.baidu/s/1rbI2hwXpMA-Pb-i-zCdVWA
提取码:cenz
不加锁
#include <unistd.h>
#include <fcntl.h>
#include <pthread.h>
#include <iostream>
using namespace std;
int value = 0;
void * mythread(void * arg)
{
int i,tmp;
for(i=0;i<1000000;i++)
{
tmp = value;
tmp++;
value = tmp;
}
}
int main()
{
pthread_t thid0;
pthread_t thid1;
/*创建两个线程*/
pthread_create(&thid0,NULL,mythread,(void*)0);
pthread_create(&thid1,NULL,mythread,(void*)1);
/*阻塞的回收线程*/
pthread_join(thid0,NULL);
pthread_join(thid1,NULL);
cout<<"value = "<<value<<endl;
return 0;
}
在不加锁的情况下,得到的结果和期待的结果不同,我们期待的结果是2000000。
加锁-使用pthread库中的互斥锁
#include <unistd.h>
#include <fcntl.h>
#include <pthread.h>
#include <iostream>
using namespace std;
int value = 0;
pthread_mutex_t mutex; /*定义锁*/
void * mythread(void * arg)
{
int i,tmp;
for(i=0;i<1000000;i++)
{
pthread_mutex_lock(&mutex); /*上锁*/
tmp = value;
tmp++;
value = tmp;
pthread_mutex_unlock(&mutex); /*释放锁*/
}
}
int main()
{
pthread_t thid0;
pthread_t thid1;
/*初始化锁*/
pthread_mutex_init(&mutex,NULL);
pthread_create(&thid0,NULL,mythread,(void*)0);
pthread_create(&thid1,NULL,mythread,(void*)1);
pthread_join(thid0,NULL);
pthread_join(thid1,NULL);
/*销毁锁*/
pthread_mutex_destroy(&mutex);
cout<<"value = "<<value<<endl;
return 0;
}
加了锁以后,得到了正确结果。
加锁-使用CriticalSection
#define WEBRTC_POSIX
#include <unistd.h>
#include <fcntl.h>
#include <pthread.h>
#include <iostream>
#include "critical_section.h"
using namespace std;
int value = 0;
rtc::CriticalSection critsect_;
void * mythread(void * arg)
{
int i,tmp;
for(i=0;i<1000000;i++)
{
critsect_.Enter();
tmp = value;
tmp++;
value = tmp;
critsect_.Leave();
}
}
int main()
{
pthread_t thid0;
pthread_t thid1;
pthread_create(&thid0,NULL,mythread,(void*)0);
pthread_create(&thid1,NULL,mythread,(void*)1);
pthread_join(thid0,NULL);
pthread_join(thid1,NULL);
cout<<"value = "<<value<<endl;
return 0;
}
使用WebRTC封装的锁CriticalSection,但这种方式不太地道。
加锁-使用CritScope
#define WEBRTC_POSIX
#include <unistd.h>
#include <fcntl.h>
#include <pthread.h>
#include <iostream>
#include "critical_section.h"
using namespace std;
int value = 0;
rtc::CriticalSection critsect_; /*定义锁*/
void * mythread(void * arg)
{
int i,tmp;
for(i=0;i<1000000;i++)
{
{ /*加括号,形成小的作用域。*/
rtc::CritScope cs(&critsect_); /*上锁*/
tmp = value;
tmp++;
value = tmp;
} /*释放cs对象,并释放锁。*/
}
}
int main()
{
pthread_t thid0;
pthread_t thid1;
pthread_create(&thid0,NULL,mythread,(void*)0);
pthread_create(&thid1,NULL,mythread,(void*)1);
pthread_join(thid0,NULL);
pthread_join(thid1,NULL);
cout<<"value = "<<value<<endl;
return 0;
}
这种方式才是使用WebRTC中锁,最正确的方式。定义了CritScope对象,在其作用域内都是上锁的,离开作用域就释放锁。
加锁-使用GlobalLock
#define WEBRTC_POSIX
#include <unistd.h>
#include <fcntl.h>
#include <pthread.h>
#include <iostream>
#include "critical_section.h"
using namespace std;
int value = 0;
rtc::GlobalLock lock; /*定义锁*/
void * mythread(void * arg)
{
int i,tmp;
for(i=0;i<1000000;i++)
{
{
rtc::GlobalLockScope ls(&lock); /*上锁*/
tmp = value;
tmp++;
value = tmp;
} /*释放锁*/
}
}
int main()
{
pthread_t thid0;
pthread_t thid1;
pthread_create(&thid0,NULL,mythread,(void*)0);
pthread_create(&thid1,NULL,mythread,(void*)1);
pthread_join(thid0,NULL);
pthread_join(thid1,NULL);
cout<<"value = "<<value<<endl;
return 0;
}
GlobalLock实现的锁紧用于保护全局变量
,不用作其他用途。从实现原理上,CriticalSection和
GlobalLock底层实现上是不一样的。
CriticalSection源码分析
在实现区域锁
CritScope、GlobalLockScope的时候,使用到了资源获取即初始化(RAII)。关于RAII
,在《WebRTC源码分析之智能指针scoped_refptr》中有介绍。
CriticalSection类
CriticalSection用于定义一把跨平台的锁。
类的声明
class RTC_LOCKABLE CriticalSection
{
public:
CriticalSection();
~CriticalSection();
/*阻塞的进入临界区*/
void Enter() const RTC_EXCLUSIVE_LOCK_FUNCTION();
/*非阻塞的进入临界区,无法获取锁,不阻塞。*/
bool TryEnter() const RTC_EXCLUSIVE_TRYLOCK_FUNCTION(true);
/*离开临界区*/
void Leave() const RTC_UNLOCK_FUNCTION();
private:
bool CurrentThreadIsOwner() const;
mutable pthread_mutex_t mutex_; /*定义互斥锁*/
mutable PlatformThreadRef thread_;
mutable int recursion_count_; /*记录递归次数*/
};
RTC_LOCKABLE、RTC_EXCLUSIVE_LOCK_FUNCTION()、RTC_UNLOCK_FUNCTION()、RTC_EXCLUSIVE_TRYLOCK_FUNCTION(true) 这几个宏展开后都和__attribute__有关,这些宏都是给编译器用的,现在就不详细介绍了,之后会写一篇文章单独介绍这些内容。
构造器和析构器
CriticalSection::CriticalSection()
{
/*定义互斥锁的属性*/
pthread_mutexattr_t mutex_attribute;
pthread_mutexattr_init(&mutex_attribute);
/*将互斥锁设置为递归的互斥锁*/
pthread_mutexattr_settype(&mutex_attribute, PTHREAD_MUTEX_RECURSIVE);
/*初始化互斥锁*/
pthread_mutex_init(&mutex_, &mutex_attribute);
/*销毁互斥锁的属性*/
pthread_mutexattr_destroy(&mutex_attribute);
CS_DEBUG_CODE(thread_ = 0);
CS_DEBUG_CODE(recursion_count_ = 0);
RTC_UNUSED(thread_);
RTC_UNUSED(recursion_count_);
}
CriticalSection::~CriticalSection()
{
/*销毁互斥锁*/
pthread_mutex_destroy(&mutex_);
}
CriticalSection是一把递归锁,linux提供的锁默认属性是非递归锁,所以需要通过锁的属性将锁设置为递归锁。
递归锁就是同一个线程可以多次对同一把锁上锁。同一个线程对同一把非递归锁和递归锁的使用方式:
非递归锁
:上锁 —— 解锁 —— 上锁 —— 解锁 —— 上锁 —— 解锁 …
递归锁
:上锁 —— 上锁 —— 上锁 —— 解锁 —— 解锁 —— 解锁 …
CurrentThreadIsOwner函数
bool CriticalSection::CurrentThreadIsOwner() const
{
#if CS_DEBUG_CHECKS
/*用于比较thread_保存的线程是否和现在的线程相等*/
return IsThreadRefEqual(thread_, CurrentThreadRef());
#else
return true;
#endif
}
CurrentThreadRef()函数返回当前线程的id,IsThreadRefEqual()函数用于比较线程id是否相等。其中thread_是CriticalSection类的数据成员。
Enter函数
void CriticalSection::Enter() const RTC_EXCLUSIVE_LOCK_FUNCTION()
{
/*上锁*/
pthread_mutex_lock(&mutex_);
#if CS_DEBUG_CHECKS
if (!recursion_count_) /*若是线程第一次使用锁,则记录线程的id。*/
{
RTC_DCHECK(!thread_);
thread_ = CurrentThreadRef();
}
else
{
/*若发生了递归,判断是否是同一线程在递归调用互斥锁。*/
RTC_DCHECK(CurrentThreadIsOwner());
}
/*记录递归的次数*/
++recursion_count_;
#endif
}
Enter()函数会调用linux系统接口pthread_mutex_lock上锁。pthread_mutex_lock函数在获取不到锁时,会阻塞线程,直到获取到锁。
当递归的上锁时,需要判断是否是同一线程,只有同一个线程才能递归的给同一把锁多次上锁。同时需要记录锁被递归的次数,上锁次数和解锁次数要保持一致。
TryEnter函数
bool CriticalSection::TryEnter() const RTC_EXCLUSIVE_TRYLOCK_FUNCTION(true)
{
/*非阻塞上锁*/
if (pthread_mutex_trylock(&mutex_) != 0)
return false;
#if CS_DEBUG_CHECKS
if (!recursion_count_)
{
RTC_DCHECK(!thread_);
thread_ = CurrentThreadRef();
}
else
{
RTC_DCHECK(CurrentThreadIsOwner());
}
++recursion_count_;
#endif
return true;
}
TryEnter()函数也是用于上锁的,但Enter()函数不同,当无法获取锁时,不会阻塞线程,而是直接返回false。
Leave函数
void CriticalSection::Leave() const RTC_UNLOCK_FUNCTION()
{
/*保证上锁和解锁是同一个线程*/
RTC_DCHECK(CurrentThreadIsOwner());
#if CS_DEBUG_CHECKS
/*递归次数减一*/
--recursion_count_;
RTC_DCHECK(recursion_count_ >= 0);
if (!recursion_count_)
thread_ = 0; /*锁全部解锁以后,将线程置为零。*/
#endif
/*解锁*/
pthread_mutex_unlock(&mutex_);
}
Leave()函数用于释放锁。
CritScope类
类的声明
class RTC_SCOPED_LOCKABLE CritScope
{
public:
explicit CritScope(const CriticalSection* cs) RTC_EXCLUSIVE_LOCK_FUNCTION(cs);
~CritScope() RTC_UNLOCK_FUNCTION();
private:
const CriticalSection* const cs_; /*托管的锁*/
RTC_DISALLOW_COPY_AND_ASSIGN(CritScope);
};
CritScope类定义的对象就是一把区域锁
,CritScope对象需要CriticalSection对象(锁对象)配合使用,CritScope对象仅仅是对锁对象的托管。
区域锁
利用的是RAII。定义CritScope对象时,会在其构造器中上锁,在CritScope对象离开其作用域时,其析构器会释放锁。
RTC_DISALLOW_COPY_AND_ASSIGN(CritScope);用于禁用拷贝构造和赋值运算符重载,其展开过程及结果如下:
#define RTC_DISALLOW_COPY_AND_ASSIGN(TypeName) \
TypeName(const TypeName&) = delete; \
RTC_DISALLOW_ASSIGN(TypeName)
#define RTC_DISALLOW_ASSIGN(TypeName) \
TypeName& operator=(const TypeName&) = delete
展开后的结果:
CritScope(const CritScope&) = delete;
CritScope& operator=(const CritScope&) = delete;
构造器和析构器
CritScope::CritScope(const CriticalSection* cs)
: cs_(cs)
{
cs_->Enter();
}
CritScope::~CritScope()
{
cs_->Leave();
}
CritScope类仅有有参构造器,所以在构造CritScope对象时必须提供CriticalSection对象(锁对象)。
在构造器中上锁,析构器中释放锁,实现了锁对象的托管。
TryCritScope类
class TryCritScope
{
public:
explicit TryCritScope(const CriticalSection* cs);
~TryCritScope();
bool locked() const __attribute__((__warn_unused_result__));
private:
const CriticalSection* const cs_; /*托管的锁*/
const bool locked_; /*是否上锁*/
mutable bool lock_was_called_; /*是否调用过locked()函数*/
RTC_DISALLOW_COPY_AND_ASSIGN(TryCritScope);
};
TryCritScope::TryCritScope(const CriticalSection* cs)
: cs_(cs), locked_(cs->TryEnter())
{
CS_DEBUG_CODE(lock_was_called_ = false);
RTC_UNUSED(lock_was_called_);
}
TryCritScope::~TryCritScope()
{
/*若没有调用过locked()函数,则断言失败。*/
CS_DEBUG_CODE(RTC_DCHECK(lock_was_called_));
if (locked_) /*如果之前上锁成功,才释放锁。*/
cs_->Leave();
}
/*判断是否上锁*/
bool TryCritScope::locked() const
{
CS_DEBUG_CODE(lock_was_called_ = true);
return locked_;
}
TryCritScope和CritScope类似,CritScope类对象在上锁时,无法获取锁时线程被阻塞。TryCritScope对象无法获取锁时,线程不会被阻塞,获取锁失败会直接返回false。
生成TryCritScope对象后,可能上锁失败,需要通过TryCritScope类对象调用locked()函数判断上锁是否成功,并且这一步是必须要做的。如果不调用locked()函数,TryCritScope类对象在析构时会断言失败。
GlobalLockPod类
class RTC_LOCKABLE GlobalLockPod
{
public:
void Lock() RTC_EXCLUSIVE_LOCK_FUNCTION();
void Unlock() RTC_UNLOCK_FUNCTION();
volatile int lock_acquired;
};
void GlobalLockPod::Lock()
{
const struct timespec ts_null = {0}; /*0秒*/
while (AtomicOps::CompareAndSwap(&lock_acquired, 0, 1))
{
nanosleep(&ts_null, nullptr); /*和sleep(0)类似*/
}
}
void GlobalLockPod::Unlock()
{
int old_value = AtomicOps::CompareAndSwap(&lock_acquired, 1, 0);
/*若未上锁就解锁,old_value值为0,断言失败。*/
RTC_DCHECK_EQ(1, old_value) << "Unlock called without calling Lock first";
}
GlobalLockPod类仅用于保护全局变量,底层实现上也和CriticalSection类不同。
CompareAndSwap函数对lock_acquired的操作是原子操作。
先介绍两个知识点,再说一下GlobalLockPod是如何工作的。
int CompareAndSwap(volatile int* i, int old_value, int new_value) 用于原子的操作i变量
,当*i的值和old_value值相等时,将new_value的值赋给*i,这个函数的返回值*i的初始值。
这个函数在逻辑上等价于以下代码:
int CompareAndSwap(volatile int* i, int old_value, int new_value)
{
int tmp = *i;
if(tmp == old_value)
*i = new_value;
return tmp;
}
使用示例如下:
#include <iostream>
#include "atomic_ops.h"
using namespace std;
int main()
{
volatile int i = 10;
volatile int j = 22;
int reti = rtc::AtomicOps::CompareAndSwap(&i,10,100);
int retj = rtc::AtomicOps::CompareAndSwap(&j,20,200);
cout<<"i = "<<i<<endl;
cout<<"j = "<<j<<endl;
cout<<"reti = "<<reti<<endl;
cout<<"retj = "<<retj<<endl;
return 0;
}
nanosleep(&ts_null, nullptr) 将线程挂起0秒,看似线程挂起0秒毫无意义。nanosleep(&ts_null, nullptr)和sleep(0)等价,sleep(0)不是将线程挂起0秒,线程会让出CPU,重新回到就绪队列,再次竞争CPU。
假设每个线程在CPU中执行的时间片为5ms,若每个线程都充分利用这5ms,如上图。若线程B执行1ms后调用了sleep(0)后,让出了CPU,线程A和线程B重新竞争CPU,每次线程B仅使用CPU1ms。
假设线程A和线程B使用GlobalLockPod类保护的全局变量。在线程上锁之前,GlobalLockPod类的数据成员lock_acquired被设置为0。若线程A先获取CPU的执行权,调用Lock()函数上锁,lock_acquired的值为0,执行到while (AtomicOps::CompareAndSwap(&lock_acquired, 0, 1))时,lock_acquired被设置为了1,AtomicOps::CompareAndSwap(&lock_acquired, 0, 1)返回0
,不执行while循环,Lock()执行完毕。线程A获取到了锁。在线程A释放锁之前,线程B获取CPU的执行权,也调用Lock()函数上锁。此时lock_acquired的值为1,执行到while (AtomicOps::CompareAndSwap(&lock_acquired, 0, 1))时,lock_acquired的值不变,AtomicOps::CompareAndSwap(&lock_acquired, 0, 1)返回1,进入while循环。线程B调用nanosleep(&ts_null, nullptr)让出CPU的执行权,重新进入就绪队列,再次竞争CPU的执行权。及时调用nanosleep(&ts_null, nullptr)的好处是避免了忙等,假设线程B有5ms的CPU执行权限,若不调用nanosleep(&ts_null, nullptr)函数,则这5ms的时间将一直执行while循环。若调用nanosleep(&ts_null, nullptr)函数,while执行一次线程B就让出CPU的使用权,这样可以更高效的利用CPU。
在线程A没有释放锁之前,线程B虽然会一直执行while循环,但每次只执行一次while循环就让出CPU,所以效率也是很高的。线程A调用Unlock()函数解锁,lock_acquired被置为0,此时线程B就可以获取到锁跳出while循环。
GlobalLock类
class GlobalLock : public GlobalLockPod
{
public:
GlobalLock();
};
GlobalLock::GlobalLock()
{
lock_acquired = 0;
}
GlobalLock在构造器中将lock_acquired置为0,在第一次使用锁之前,需要将lock_acquired置为0。
GlobalLockScope类
class RTC_SCOPED_LOCKABLE GlobalLockScope
{
public:
explicit GlobalLockScope(GlobalLockPod* lock)
RTC_EXCLUSIVE_LOCK_FUNCTION(lock);
~GlobalLockScope() RTC_UNLOCK_FUNCTION();
private:
GlobalLockPod* const lock_;
RTC_DISALLOW_COPY_AND_ASSIGN(GlobalLockScope);
};
GlobalLockScope::GlobalLockScope(GlobalLockPod* lock)
: lock_(lock)
{
lock_->Lock();
}
GlobalLockScope::~GlobalLockScope()
{
lock_->Unlock();
}
GlobalLockScope类和CritScope类功能类似都是区域锁
,CritScope使用的是CriticalSection锁,GlobalLockScope使用的是GlobalLockPod锁。
小结
本文分析了WebRTC中的锁,CriticalSection和GlobalLockPod是使用不同方式实现的两把锁,在这两把锁的基础上提供了多个区域锁
,CritScope、TryCritScope、GlobalLockScope。
本文标签: 源码webrtcCRITICALSECTION
版权声明:本文标题:WebRTC源码分析之锁-CriticalSection 内容由热心网友自发贡献,该文观点仅代表作者本人, 转载请联系作者并注明出处:https://m.elefans.com/dongtai/1726269023a1063588.html, 本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌抄袭侵权/违法违规的内容,一经查实,本站将立刻删除。
发表评论