《C++ Concurrency in Action》笔记8 死锁(1)
来源:互联网 发布:学生化妆品推荐 知乎 编辑:程序博客网 时间:2024/06/10 08:21
就像之前关于top和pop的讨论,他的问题本质上来说是因为lock了太小的单元,保护并不能覆盖几个操作的组合。但是,另一方面,如果lock了太大的范围,同样也能导致问题。极端的情况就是一个全局mutex对象保护了所有的共享数据。在一个存在着大量共享数据的系统中,这将抵消并发带来的任何效益增值,因为它强迫在同一时刻只能运行一个线程,即使这些线程访问的是不同的共享数据。Linux内核的第一个版本就是被设计成使用一个全局内核锁。尽管他能工作,但是这意味着一个双核系统的性能明显低于两个单核系统。后来的Linux版本采用了更加细粒度的锁策略,解决了这个问题。
使用细粒度锁方案的一个问题就是,为了在一个操作中保护所有数据,你需要更多的mutex。如前所说,有时候需要增加锁粒度,以至只需要一个mutex去执行锁定操作。但有时候这是不可取的,例如多个mutex保护了一个类的多个实例。
In this case, locking at the next level up would mean either leaving the locking to the user or having a single mutex that protected all instances of that class, neither of which is particularly desirable.
在这种情况下,进入下一层锁就意味着要么将锁丢给用户去执行,要么使用一个单独的mutex去保护这个类的所有实例,但两者都不令人满意。(不知道翻译的是否准确)
如果你最终不得不在一个操作中使用多个mutex,那么还有另外一个隐藏的风险:死锁。这种情况几乎完全与race condition相反,多个线程都想争得控制权,但却都在等待对方释放控制权,结果只能无限等下去。
一种常见的避免死锁的方式是,总是以同样的顺序锁定2个mutex。但有时不太容易,例如多个mutex负责保护一个类的多个实例。想象一个例子:一个操作用于交换同一个类的两个实例,如果选择固定顺序(例如,先是锁定与第一个参数对应的mutex,然后再锁定与第二个参数对应的mutex),那将适得其反:如果两个线程同时针对相同的一对实例进行交换操作,只是参数顺序不同,那么将产生死锁。
幸运的是,C++标准库提供了一个函数来解决这种问题:std::lock(),可以一次性锁定2个或更多的mutex,而且不会导致死锁。下面的示例演示如何使用这个函数:
class some_big_object{};class X{private:some_big_object some_detail;std::mutex m;public:X(some_big_object const& sd) :some_detail(sd) {}friend void swap(X& lhs, X& rhs){if (&lhs == &rhs)return;std::lock(lhs.m, rhs.m);std::lock_guard<std::mutex> lock_a(lhs.m, std::adopt_lock);std::lock_guard<std::mutex> lock_b(rhs.m, std::adopt_lock);swap(lhs.some_detail, rhs.some_detail);}};
首先检查两个参数是否不同,因为试图锁定一个已经被自己锁定的mutex是未定义行为( std::recursive_mutex可以被同一个线程多次锁定)。然后使用std::lock()函数锁定两个mutex,再构造2个std::lock_guard对象,std::adopt_lock参数用于通知std::lock_guard对象:为其提供的mutex已经被锁定,在构造时不要再次锁定mutex参数。然后进行具体的数据交换。当函数执行完毕后,2个std::lock_guard对象的析构函数负责解锁其对应的mutex对象,这一点我们通过查看std::lock_guard的源码(vs2013)可以看得十分清楚:
template<class... _Mutexes>class lock_guard{// class with destructor that unlocks mutexespublic:explicit lock_guard(_Mutexes&... _Mtxes): _MyMutexes(_Mtxes...){// construct and lock_STD lock(_Mtxes...);}lock_guard(_Mutexes&... _Mtxes, adopt_lock_t): _MyMutexes(_Mtxes...){// construct but don't lock}~lock_guard() _NOEXCEPT{// unlock all_For_each_tuple_element(_MyMutexes,[](auto& _Mutex) _NOEXCEPT { _Mutex.unlock(); });}lock_guard(const lock_guard&) = delete;lock_guard& operator=(const lock_guard&) = delete;private:tuple<_Mutexes&...> _MyMutexes;};
此处仅将lock_guard类模板的部分定义贴了出来,还有两个模板特化代码没有贴出来分别是只有一个mutex参数和没有参数的定义。如果没有参数则lock_guard什么也不做。
具有std::adopt_lock类型参数的构造函数,其实这个参数仅仅起到一个通知的作用,此时构造函数不去对mutex执行lock操作。注意析构函数,无论如何都执行mutex的unlock操作。通过分析std::lock_guard的源码,也会知道,std::lock()函数仅仅负责锁定其参数对应的mutex,别的什么也不做。解锁是由std::lock_guard来做的。
注意多参数模板定义的构造函数,在函数体内自动调用了std::lock()函数。因此上面的swap()函数可以简化成如下版本:
void swap1(X& lhs, X& rhs){if (&lhs == &rhs)return;lock_guard<mutex,mutex> lg(lhs.m, rhs.m);swap(lhs.some_detail, rhs.some_detail);}
还要注意的是,std::lock()函数在试图锁定一个mutex时可能引发异常,例如,当成功锁定一个mutex后在试图锁定第二个mutex时即使产生来了异常一会保证释放第一个mutex。它遵循一个原则:要么都锁定要么都不锁定。
虽然,std::lock()函数可以帮你同时锁定多个mutex,但是它无法解决这些mutex同时被其他线程分别单独锁定而带来的问题,此时只能靠一个程序员的经验了。
- 《C++ Concurrency in Action》笔记8 死锁(1)
- 《C++ Concurrency in Action》笔记8 死锁(2)避免死锁
- 《C++ Concurrency in Action》笔记1 join和detach
- 《C++ Concurrency in Action》笔记7 mutex(1)
- 《C++ Concurrency in Action》笔记29 设计并行代码(1)
- C plus plus Concurrency in Action
- 读C++ concurrency in action笔记
- 《C++ concurrency in action》第二章笔记
- 《C++ Concurrency in Action》笔记 前言
- 《C++ Concurrency in Action》笔记4 hardware_concurrency()
- 《C++ Concurrency in Action》笔记14 condition_variable
- 《C++ Concurrency in Action》笔记16 future
- 《C++ Concurrency in Action》笔记17 promise
- 《C++ Concurrency in Action》笔记2 线程函数传参(1)
- 《C++ Concurrency in Action》笔记3 move线程对象
- 《C++ Concurrency in Action》笔记4 vector<thread>
- 《C++ Concurrency in Action》笔记5 std::thread::id
- 《C++ Concurrency in Action》笔记6 Avoiding problematic race conditions
- 2017 ACM-ICPC 亚洲区(西安赛区)网络赛 B Coin (概率计算)
- s17day18django的查询.ajax和分页
- 欢迎使用CSDN-markdown编辑器
- HTML5基础知识
- hibernate映射关系
- 《C++ Concurrency in Action》笔记8 死锁(1)
- spring框架(容器框架之一)事务管理
- 如何写SysV服务管理脚本
- java : jaxws 查询天气预报
- 番茄工作法——应变(笔记)
- Maven学习笔记(一)------安装
- 1. 引子
- Java面试笔试指南(六)---容器和多线程
- Python环境搭建