std::get<C++11多线程库~线程间共享数据>(10):使用互斥量保护共享数据(4)

  1 #ifndef DEADLOCK_QUESTIONDESCRIBLE_AND_SOLUTION_H
  2 #define DEADLOCK_QUESTIONDESCRIBLE_AND_SOLUTION_H
  3 
  4 /*
  5  * 话题1:使用互斥量保护共享数据
  6  *
  7  * 接下来学习第四个小话题:死锁,死锁的问题描述,死锁的解决方案
  8  *
  9  *      互斥量在解决共享数据安全的同时,除了会引入条件竞争, 还可能会引发死锁。
 10  *      条件竞争使得共享数据变得不安全,而死锁会导致各线程僵持,导致线程间出现互相等待的尴尬局面。
 11  *
 12  *      下面我们就来学习一下, 出现死锁的场景,以及如何避免死锁的发生。
 13  *
 14 */
 15 
 16 /*
 17  * 死锁的问题描述:
 18  *
 19  *      线程内出现两个或两个以上的互斥量时, 比如互斥量ma和mb, 有两个线程TA和TB, 线程TA先锁住了ma,准备继续对mb上锁, 而正在此时,
 20  * 线程TB锁住了mb,准备继续对ma上锁。 线程TA等待着线程TB释放互斥量mb,线程TB等待线程TA释放互斥量ma, 谁也不让谁,谁也不罢休,两个线程
 21  * 僵持等待。这就是死锁发生的情况。
 22 */
 23 
 24 
 25 /*
 26  * 死锁的解决方案:
 27  *
 28  *      一般的建议:让多个互斥量总是以相同的顺序上锁。互斥量mb总是在ma上锁之后上锁。可以将这个建议套入上面对死锁问题的描述,你就能够领悟了。
 29  *
 30  *      一般的建议往往只能搞定一般的问题, 如果多个互斥量所属于同一个作用域内,那么这个一般的建议是可以搞定的。那么多个互斥量时,如何能保证
 31  * 不出问题,一定不会出现死锁呢?
 32  *
 33  *      C++ 标准库提供了 std::lock, 可以一次性的锁住多个(两个及两个以上)互斥量,并且没有副作用(没有死锁风险)。std::lock的原则是,
 34  * 要么对所有互斥量都成功上锁,要么一个互斥量也不上锁。
 35 */
 36 
 37 #include <mutex>
 38 struct Data{
 39 
 40 };
 41 void swap(Data & ldata, Data &rdata);
 42 bool greater(Data &ldata, Data & rdata);
 43 
 44 class DeadLock_QuestionDescrible_and_Solution;
 45 void swap(DeadLock_QuestionDescrible_and_Solution &lobj, DeadLock_QuestionDescrible_and_Solution & robj);
 46 bool greater(DeadLock_QuestionDescrible_and_Solution &lobj, DeadLock_QuestionDescrible_and_Solution & robj);
 47 
 48 class DeadLock_QuestionDescrible_and_Solution
 49 {
 50     Data m_data;
 51     std::mutex m_mutex;
 52 public:
 53     DeadLock_QuestionDescrible_and_Solution(Data data):m_data(data){
 54 
 55     }
 56 
 57     friend void swap(DeadLock_QuestionDescrible_and_Solution &lobj, DeadLock_QuestionDescrible_and_Solution & robj)
 58     {
 59         if (&lobj == &robj){
 60             return ;
 61         }
 62         else{
 63             std::lock(lobj.m_mutex, robj.m_mutex);
 64             std::lock_guard<std::mutex> l_guard(lobj.m_mutex, std::adopt_lock);
 65             std::lock_guard<std::mutex> r_guard(robj.m_mutex, std::adopt_lock);
 66             swap(lobj.m_data, robj.m_data);
 67         }
 68     }
 69 
 70     friend bool greater(DeadLock_QuestionDescrible_and_Solution &lobj, DeadLock_QuestionDescrible_and_Solution & robj)
 71     {
 72         if (&lobj == &robj){
 73             return false;
 74         }
 75         else{
 76             std::lock(lobj.m_mutex, robj.m_mutex);
 77             std::lock_guard<std::mutex> l_guard(lobj.m_mutex, std::adopt_lock);
 78             std::lock_guard<std::mutex> r_guard(robj.m_mutex, std::adopt_lock);
 79             return greater(lobj.m_data, robj.m_data);
 80         }
 81     }
 82 };
 83 
 84 /*
 85  *      上边这个例子很好的阐明了, "一般的建议只能搞定一般的问题"。
 86  * 上边例子中 DeadLock_QuestionDescrible_and_Solution::swap 和 DeadLock_QuestionDescrible_and_Solution::greater 两个函数
 87  * 很好的说明了问题。
 88  *
 89  *      我们来分析一下, 每一个 Data 实例都有一个互斥量保护, 在 swap 和 greater 函数中,就都出现了 两个实例和两个互斥量。
 90  *
 91  *      咋一看,如果调用 swap 和 greater 函数时,参数按相同的顺序传递,似乎也不会出现死锁, 但实际上,我们把接口提供给用户,用户是很难保证
 92  * 多个参数按相同顺序调用我们提供的接口。
 93  *
 94  *      比如, 线程TA 调用 swap() 函数,按序传递 objA objB;线程TB 调用 greater()函数,按序传递 ojbB objA。 死锁就诞生了!
 95  *
 96  *      对于,一般建议提到的做法,按序对互斥量上锁, 下边给出一个例子。
 97  *
 98 */
 99 
100 struct OtherData{
101 
102 };
103 
104 class OtherObject_ThreadSafe{
105     Data m_data;
106     std::mutex m_data_mutex;
107 
108     OtherData m_otherData;
109     std::mutex m_otherData_mutex;
110 
111     void func1(){
112         std::lock_guard<std::mutex> guard_data(m_data_mutex);
113         std::lock_guard<std::mutex> guard_otherData(m_otherData_mutex);
114 
115         //m_data and m_otherData do something...
116     }
117 
118     void func2(){
119         std::lock_guard<std::mutex> guard_data(m_data_mutex);
120         std::lock_guard<std::mutex> guard_otherData(m_otherData_mutex);
121 
122         //m_data and m_otherData do something...
123     }
124 };
125 
126 /*
127  * 上边例子演示了,同一作用域内,需要对多个互斥量上锁,可以采用一般的建议,按相同的顺序对互斥量进行上锁,就可以搞定即保护了共享数据,又不会出现死锁。
128  *
129  *
130  * 友情提醒: 实际编程中,能要底层级别手段搞定的问题,就尽量不要用高级的东西,毕竟越高级代价也就越高!!!
131 */
132 
133 
134 /*
135  * 拓展:
136  * C++17 对组合使用 std::lock()函数 和 std::lock_guard 模板类 提供了另一个支持,那便是: std::scoped_lock模板类
137  *
138  * 因此, 上边的 swap() 函数可以改写成如下形式。
139 */
140 
141 friend void swap(DeadLock_QuestionDescrible_and_Solution &lobj, DeadLock_QuestionDescrible_and_Solution & robj)
142 {
143     if (&lobj == &robj){
144         return ;
145     }
146     else{
147         std::scoped_lock sl(lobj.m_mutex, robj.m_mutex);
148         swap(lobj.m_data, robj.m_data);
149     }
150 }
151 
152 /*
153  *      std::scoped_lock<>一种新的RAII类型模板类型,与std::lock_guard<>的功能等价,
154  * 这个新类型能接受不定数量的互斥量类型作为模板参数,以及相应的互斥量(数量和类型)作为构造参数。
155  * 互斥量支持构造即上锁,与std::lock的用法相同,其解锁阶段是在析构中进行。
156 */
157 #endif // DEADLOCK_QUESTIONDESCRIBLE_AND_SOLUTION_H
原文地址:https://www.cnblogs.com/azbane/p/15483838.html