C++11 线程库—互斥锁

前言

多线程因其调度的随机性和时间片分配,如果没有限制的访问临界资源,会导致出现无法预测的结果,也无法达到预期。
所以,访问临界区,需要是原子性的,在一个线程完成之前,不能有其他线程访问,影响。
互斥量的底层原理可以参看[Linux]线程互斥

在这里插入图片描述

在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()解锁

注意,线程函数调用lock()时,可能会发生以下三种情况:

  • 如果该互斥锁当前没有被锁住,则调用线程将获取到互斥锁,直到调用unlock之前,该线程一直拥有该锁
  • 如果当前互斥锁被其他线程锁住,则当前调用线程会被阻塞在获取锁的lock()函数处
  • 如果当前互斥锁被当前调用线程锁住,则会产生死锁(deadlock)

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

在这里插入图片描述

其允许同一个线程对互斥锁多次上锁(即递归上锁),来获得互斥量对象的多层所有权,释放互斥锁时需要调用与该锁层次深度相同次数的unlock()。除此之外,基本用法同mutex

三. timed_mutex

timed_mutex是定时互斥锁

在这里插入图片描述

多提供了try_lock_for和try_lock_until

  • try_lock_for:
    接受一个时间范围,表示在一段时间范围之内线程如果没有获得锁则被阻塞住,如果在此期间其他线程释放了锁,则该线程可以获得互斥锁,如果超时(即在时间范围内没有获取到锁),则返回false
  • try_lock_until:
    接受一个时间点作为参数,在指定时间点未到来之前线程如果没有获取到锁,则一直阻塞,如果在此期间其他线程释放了锁,则该线程可以获得互斥锁,如果超时(即在时间范围内没有获取到锁),则返回false

四. 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还支持手动加锁和解锁

在这里插入图片描述

  • 上锁/解锁操作:lock,try_lock,try_lock_for,try_lock_until和unlock
  • 修改操作:移动赋值交换swap(与另一个unique_lock对象互换锁管理的互斥锁所有权),release(返回它所管理的互斥锁对象的指针,并释放所有权)
  • 获取属性:owns_lock(返回当前对象是否上锁),operator bool()(与owns_lock功能相同),mutex(返回当前unique_lock所管理的互斥锁的指针)

结束语

感谢你的阅读

如果觉得本篇文章对你有所帮助的话,不妨点个赞支持一下博主,拜托啦,这对我真的很重要。
在这里插入图片描述

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值