【C++多线程】lock_guard<T>类和unique_lock<T>类
lock_guard<T>
使用的RAII机制,互斥量在lock_guard<T>对象构造时lock,在其析构时unlock。主要是为了解决在手动lock和unlock时忘记unlock的情况。lock_guard
类不可复制,因为一个对象只能关联一个mutex。
锁的粒度
锁的粒度用来描述通过一个锁保护着的数据量大小。一个细粒度锁能够保护较小的数据量,一个粗粒度锁能够保护较多的数据量。选择粒度对于锁来说很重要,为了保护对应的数据,保证锁有能力保护这些数据也很重要。所以一如了更加灵活的unique_lock<T>。
unique_lock<T>
类unique_lock<T> 是通用互斥包装器,允许延迟锁定、锁定的有时限尝试、递归锁定、所有权转移和与条件变量一同使用。unique_lock<T>可移动,不可复制。unique_lock<T>能够在需要是lock,用完后unlock,当生命周期结束时若此时互斥量没有解锁,也会像lock_guard<T>一样析构解锁。也就是说类unique_lock<T>是类lock_guard<T>的一个超集。unique_lock<T>相比lock_guard<T>更加灵活,但是效率差一些,因为占用更多的内存。以下是cppreference.com对unique_lock<T>的说明。
随时unlock和lock
1 #include <iostream> 2 #include <thread> 3 #include <mutex> 4 #include <list> 5 using namespace std; 6 7 class A { 8 public: 9 void input() 10 { 11 for (int i = 0; i < 1000; i++) 12 { 13 // lock_guard<mutex> guard(my_mutex); 14 unique_lock<mutex> my_lock(my_mutex); //构造时自动lock 15 ilst.push_back(i); 16 my_lock.unlock(); //在生命周期结束前提前unlock,为其他线程执行争取时间 17 cout << "加入数据:" << i << endl; 18 my_lock.lock(); //在需要时又重新上锁 19 20 //do something with my_mutex 21 22 } //生命周期结束,自动在析构函数中unlock 23 24 } 25 26 void output() 27 { 28 for (int i = 0; i < 1000; i++) 29 { 30 // lock_guard<mutex> guard(my_mutex); 31 unique_lock<mutex> my_lock(my_mutex); 32 if (!ilst.empty()) 33 { 34 cout << "读读读读出数据:" << ilst.front() << endl; 35 ilst.pop_front(); 36 } 37 } 38 } 39 40 private: 41 list<int> ilst; 42 mutex my_mutex; 43 }; 44 45 int main() 46 { 47 A a; 48 thread t1(&A::input, &a); 49 thread t2(&A::output, &a); 50 t1.join(); 51 t2.join(); 52 return 0; 53
}
所有权转移
unique_lock<T>类和unique_ptr类一样属于只能移动,不能复制的类型。unique_lock<T>转移所有权的方式有两种:一是使用std::move();二是添加成员函数,返回临时对象。
#include <iostream> #include <thread> #include <mutex> #include <list> using namespace std; class A { public: // 添加成员函数转移所有权 unique_lock<mutex> mymove() { return unique_lock<mutex>(my_mutex); } void input() { for (int i = 0; i < 1000; i++) { unique_lock<mutex> my_lock = mymove(); // ilst.push_back(i); cout << "加入数据:" << i << endl; } } void output() { for (int i = 0; i < 1000; i++) { unique_lock<mutex> my_lock1(my_mutex); //此时my_lock1与my_mutex关联 unique_lock<mutex> my_lock2(move(my_lock1)); //使用std::move,将my_lock2与my_mutex关联 if (!ilst.empty()) { cout << "读读读读出数据:" << ilst.front() << endl; ilst.pop_front(); } } } private: list<int> ilst; mutex my_mutex; }; int main() { A a; thread t1(&A::input, &a); thread t2(&A::output, &a); t1.join(); t2.join(); return 0; }
第二参数
unique_lock<T> 有几个第二参数,std::adopt_lock,std::try _to_lock,std::defer_lock。
std::adopt_lock,将一个已经上锁的mutex,关联到unique_lock对象,该对象咋构造时不再对mutex上锁。需要程序员自己保证在构造unique_lock对象之前已经上锁。用法示例unique_lock<std::mutex> my_lock(my_mutex, std::adopt_lock)。
std::try _to_lock,在构造对象时尝试去上锁,如果没有上锁成功,并不阻塞。用法示例unique_lock<std::mutex> my_lock(my_mutex, std::try_to_lock)
std::defer_lock,在构造对象时,不上锁。所以前提就是之前没有被上锁。
1 #include <iostream> 2 #include <thread> 3 #include <mutex> 4 #include <list> 5 using namespace std; 6 7 class A { 8 public: 9 void input() 10 { 11 for (int i = 0; i < 1000; i++) 12 { 13 //前提程序员必须先自己上锁 14 my_mutex.lock(); //前提程序员必须先自己上锁 15 unique_lock<mutex> my_lock(my_mutex, adopt_lock); //说明前面已经上锁,构造时不需要再上锁 16 ilst.push_back(i); 17 cout << "加入数据:" << i << endl; 18 } //生命周期结束,自动在析构函数中unlock 19 20 } 21 22 void output() 23 { 24 for (int i = 0; i < 1000; i++) 25 { 26 // 前提是前面没有对my_mutex上锁 27 unique_lock<mutex> my_lock(my_mutex, defer_lock); //初始化一个没有上锁的对象,后面使用是自己上锁 28 if (!ilst.empty()) 29 { 30 cout << "读读读读出数据:" << ilst.front() << endl; 31 ilst.pop_front(); 32 } 33 } 34 } 35 36 private: 37 list<int> ilst; 38 mutex my_mutex; 39 }; 40 41 int main() 42 { 43 A a; 44 thread t1(&A::input, &a); 45 thread t2(&A::output, &a); 46 t1.join(); 47 t2.join(); 48 return 0; 49 }