1 /* 2 * 话题1:使用互斥量保护共享数据 3 * 4 * 1. C++中使用互斥量 5 * 2. 用代码来保护共享数据 6 * 3. 定位接口间的条件竞争 7 * 4. 死锁:问题描述及解决方案 8 * 5. 避免思索的进阶指导 9 * 6. std::unique_lock ---- 灵活的锁 10 * 7. 不同域中互斥量所有权的传递 11 * 8. 锁的粒度 12 * 13 * 14 * 当程序中有共享数据时,你肯定不想让程序其陷入条件竞争的情况。将所有访问共享数据结构的代码都标记为互斥是否是一种更好的办法呢? 15 * 这样,任何一个线程在执行时,其他线程试图访问共享数据时,就必须进行等待。除非该线程就在修改共享数据,否则任何线程都不可能会看到修改中的共享数据。 16 * 17 * 当访问共享数据前,将数据锁住,在访问结束后,再将数据解锁。线程库需要保证,当一个线程使用特定互斥量锁住共享数据时, 18 * 其他的线程想要访问锁住的数据,都必须等到之前那个线程对数据进行解锁后,才能进行访问。这就保证了所有线程都能读写共享数据,而不会出现破坏共享数据的未定义行为。 19 * 20 * 互斥量一种数据保护通用机制,但它不是“神”;需要编排代码来保护数据的正确性,并且需要合理的设计来避免接口间的竞争条件也非常重要。 21 * 同时,互斥量自身也有问题,可能也会造成死锁,或对数据保护的太多(或太少),进而影响到程序的效率。 22 * 23 * 24 * 接下来学习第一个小话题:C++中使用互斥量 25 * 一段代码,对链表插入和读取进行保护。 26 * 27 */ 28 29 #include <QCoreApplication> 30 #include <mutex> 31 #include <list> 32 #include <algorithm> 33 34 std::list<int> list_data; //共享数据, 即被保护的数据 35 std::mutex list_mutex; 36 37 void add_to_list(int value){ 38 std::lock_guard<std::mutex> lk(list_mutex); 39 list_data.push_back(value); 40 } 41 42 bool list_contains(int value){ 43 std::lock_guard<std::mutex> lk(list_mutex); 44 return std::find(list_data.begin(), list_data.end(), value) != list_data.end(); 45 } 46 47 /* 48 * add_to_list()和list_contains()函数中使用std::lock_guard<std::mutex>,使得这两个函数中对数据的访问是互斥的,是线程安全的。 49 * list_contains()不可能读取到正在被add_to_list()修改的列表内容。 50 * 51 * 如果, 我们的函数和互斥量不是全局的,而是被类封装的,情况又会如何。C++更常规的是采用面向对象对数据进行封装。 52 */ 53 54 55 class read_write_list{ 56 std::list<int> list_data; //共享数据, 即被保护的数据 57 std::mutex list_mutex; 58 59 public: 60 void add_to_list(int value){ 61 std::lock_guard<std::mutex> lk(list_mutex); 62 list_data.push_back(value); 63 } 64 65 bool list_contains(int value){ 66 std::lock_guard<std::mutex> lk(list_mutex); 67 return std::find(list_data.begin(), list_data.end(), value) != list_data.end(); 68 } 69 70 std::list<int> & get_list_data(){ //罪恶的根源!!! 71 return list_data; //把保护的数据,以引用或者指针的形式返回,类的外部便可以对保护的数据进行操作, 被保护的数据就失去了保护。 72 } 73 }; 74 75 /* 76 * 对 add_to_list() 和 list_contains() 的访问是安全的,成员函数 get_list_data() 的出现, 让数据不在安全。 77 * 78 * 具有访问能力的指针或引用可以访问(并可能修改)被保护的数据,而不会被互斥锁限制。这就需要对接口有相当谨慎的设计,要确保互斥量能锁住数据的访问,并且不留后门。 79 */ 80 81 82 83 /* 84 * 拓展: 85 * C++17 添加了一个新特性, 称为“模板类参数推导”。 86 * 87 * 因此, std::lock_guard<std::mutex> lk(list_mutex); 就可以写成 std::lock_guard lk(list_mutex); 具体的模板参数类型推导交给C++17编译器完成。 88 * 89 * C++17 还提供了一种加强版数据保护机制: 90 * std::scoped_lock guard(list_mutex); 91 * std::scoped_lock<std::mutex> guard(list_mutex); 92 */ 93 int main(int argc, char *argv[]) 94 { 95 QCoreApplication a(argc, argv); 96 97 return a.exec(); 98 }