前面学习了std::lock_guard和std::scoped_lock,lock_guard的功能是自动解锁,能有效避免忘记unlock()的情况。但只能锁定一个互斥锁,scoped_lock比lock_guard高级,可以同时锁定多个互斥体。而unique_lock比它们更灵活。它独占管理互斥量所有权,析构时自动解锁(仅当持有锁时),且不可复制但可移动,支持延迟加锁、手动解锁/加锁、尝试加锁、超时加锁及所有权转移,是唯一能与 std::condition_variable 配合使用的锁包装器。
下面来看它的使用:
//unique_lock.cpp unique_lock example //#include <iostream> // std::cout #include <thread> // std::thread #include <mutex> // std::mutex //临界区互斥锁 std::mutex mtx; // mutex for critical section int g_count = 0; //自增函数测试 void incrementation() { printf("incrementation========================>start lock\n"); //std::lock_guard<std::mutex> lock(mtx); std::unique_lock<std::mutex> lock(mtx); for (int i = 0; i < 1000000; ++i) { g_count++; } //模拟耗时 std::this_thread::sleep_for(std::chrono::seconds(1)); //休眠1秒 printf("incrementation========================>end lock\n"); } int main() { std::thread t1(incrementation); std::thread t2(incrementation); t1.join(); t2.join(); //理论应该 2000000,(不加锁)实际永远小于这个数 printf("g_count=========%d\n", g_count); printf("hello learn unique_lock\n"); return 0; }编译运行:
这个例子说明unique_lock具有和lock_guard一样的功能。
unique_lock也可以同时锁定多个互斥体,功能与scoped_lock一样。
std::scoped_lock vs std::unique_lock 详细对比
下面来看锁定两个互斥体的示例
//unique_lock2.cpp unique_lock example //#include <iostream> // std::cout #include <thread> // std::thread #include <mutex> // std::mutex struct Box { explicit Box(int num) : num_things{ num } {} int num_things; std::mutex m; }; void transfer(Box& from, Box& to, int num) { // 仍未实际取锁 std::unique_lock lock1{ from.m, std::defer_lock }; std::unique_lock lock2{ to.m, std::defer_lock }; // 在不死锁的情况下锁定两个 unique_lock std::lock(lock1, lock2); from.num_things -= num; to.num_things += num; // 互斥体 “from.m” 和 “to.m” 会在 unique_lock 析构函数中解锁 } int main() { Box acc1(100); Box acc2(50); std::thread t1(transfer, std::ref(acc1), std::ref(acc2), 10); std::thread t2(transfer, std::ref(acc2), std::ref(acc1), 5); t1.join(); t2.join(); printf("acc1: %d\n", acc1.num_things); printf("acc2: %d\n", acc2.num_things); printf("hello learn unique_lock\n"); return 0; }编译运行:
unique_lock不仅可以自动析构时解锁,还可以随时unlock()/lock(),例如:
//unique_lock3.cpp unique_lock example随时 unlock() / lock() //#include <iostream> // std::cout #include <thread> // std::thread #include <mutex> // std::mutex #include <vector> int main() { int counter = 0; std::mutex counter_mutex; std::vector<std::thread> threads; //1.记录开始时间点 auto start = std::chrono::steady_clock::now(); auto worker_task = [&](int id) { std::unique_lock<std::mutex> lock(counter_mutex); //锁定 ++counter; printf("%d , 内部计数器: %d\n", id, counter); lock.unlock(); //解锁 // 我们模拟昂贵操作时不保有锁 std::this_thread::sleep_for(std::chrono::seconds(1)); lock.lock(); //锁定 ++counter; printf("%d , 最终计数器: %d\n", id, counter); //离开作用域自动解锁 }; for (int i = 0; i < 10; ++i) threads.emplace_back(worker_task, i); for (auto& thread : threads) thread.join(); //2.记录结束时间点 auto end = std::chrono::steady_clock::now(); //3.计算时间差 auto elapsed = end - start; printf("耗时: %lld 纳秒\n", std::chrono::duration_cast<std::chrono::nanoseconds>(elapsed).count()); printf("耗时: %lld 微秒\n", std::chrono::duration_cast<std::chrono::microseconds>(elapsed).count()); printf("耗时: %lld 毫秒\n", std::chrono::duration_cast<std::chrono::milliseconds>(elapsed).count()); printf("耗时: %lld 秒\n", std::chrono::duration_cast<std::chrono::seconds>(elapsed).count()); printf("hello learn unique_lock\n"); return 0; } //system_clock vs steady_clock //测量耗时请务必使用 steady_clock,它保证只向前走,不会被系统时间修改(如NTP校时)影响。 //不要用 system_clock 测耗时,因为用户或系统可能修改系统时间,导致测出负数或巨大偏差。system_clock 更适合用来获取日历时间(显示给用户看)编译运行:
注释掉
第20行 : lock.unlock(); //解锁
第25行:lock.lock(); //锁定
再次编译运行:
说明unique_lock功能更强。
参考:
https://zh.cppreference.com/cpp/thread/unique_lock
【C++】std::unique_lock 详解:灵活的RAII锁管理工具_std::unique+lock-CSDN博客