前言
多线程因其调度的随机性和时间片分配,如果没有限制的访问临界资源,会导致出现无法预测的结果,也无法达到预期。
所以,访问临界区,需要是原子性的,在一个线程完成之前,不能有其他线程访问,影响。
互斥量的底层原理可以参看[Linux]线程互斥
文章目录
- 前言
- 一. mutex
- 1. 构造函数
- 2. 加锁与解锁
- 二. recursive_mutex
- 三. timed_mutex
- 四. lock_guard和unique_lock
- 1. 构造函数
- 结束语
在C++11的线程库中,有很多适用于不同场景的互斥量
一. mutex
mutex是互斥锁的意思,其成员函数如下:
1. 构造函数
函数声明 | 说明 |
---|---|
constexpr mutex() noexcept | 不会抛异常的无参构造 |
mutex(const mutex&) = delete | 不支持拷贝构造 |
PS:
noexcept
在函数声明后作标识符,默认是noexcept(true),表示不会抛异常
constexpr
:让该函数在编译时生成,而不是运行时
=delete
在函数声明后,表示不会生成该函数
2. 加锁与解锁
C++11线程库其实就是对系统调用的封装,将其封装成一个类
函数声明 | 说明 |
---|---|
void lock() | 加锁 |
bool try_lock() | 尝试加锁 |
void unlock() | 解锁 |
着重讲解一下try_lock
try_lock分为3种情况
- 没有线程持有锁,则调用try_lock的线程获得锁
- 其他线程持有锁,则加锁失败,返回false
- 当前线程持有锁,不进行操作
二. recursive_mutex
关于死锁的概念,可以参看Linux死锁
如果我们在递归中使用互斥锁,就会出现死锁的情况
mutex _mutex;
void Func(int n)
{
if (n == 0)
{
return;
}
_mutex.lock();
cout<<n<<endl;
Func(n-1);
_mutex.unlock();
}
int main()
{
thread t1(Func, 7);
t1.join();
return 0;
}
为了解决这一问题,C++11提供了递归互斥锁 recursive_mutex
基本用法同mutex
三. timed_mutex
timed_mutex是定时互斥锁
多提供了try_lock_for和try_lock_until
try_lock_for:加锁情况同try_lock,但是支持加锁一段时间
try_lock_until:支持加锁到一个时间点
四. lock_guard和unique_lock
手动的加锁与解锁难免有些麻烦,于是C++11根据RAII习语管理资源,lock_guard在构造函数中自动绑定构造互斥锁,并且加锁,大大减少了死锁的风险,并且在析构函数中调用解锁,避免了忘记解锁等不必要的麻烦。
1. 构造函数
函数声明 | 说明 |
---|---|
explicit lock_guard(mutex_type&m) | 构造函数,需要传一把锁 |
lock_guard(mutex_type& m,adopt_lock tag) | 将锁转移到lock_guard的锁 |
lock_guard(const lock_guard&) = delete | 不支持拷贝构造 |
adopt_lock的作用正如他的命名,寄养锁,使用如下:
std::mutex _mutex;
void test5() {//std::adopt_mutex的大妙处
_mutex.lock();
lock_guard<std::mutex> lg(_mutex, std::adopt_lock);
//在这里进行收养锁
cout << "hello test5" << endl;
}
这样就将_mutex的锁转移到lock_guard中,由lock_guard管理
但是lock_guard只会在构造时加锁,析构时解锁,如果途中我们有解锁和需求则无法完成,所以unique_lock出现了。
unique_lock同样也遵守RAII习语管理资源,构造时加锁,析构时解锁,但是unique_lock还支持手动加锁和解锁
赋值重载operator=
的使用同thread的operator=,可以使用匿名对象创建。- 调用
release
会释放其管理的锁
结束语
感谢你的阅读
如果觉得本篇文章对你有所帮助的话,不妨点个赞支持一下博主,拜托啦,这对我真的很重要。