类 lock_guard 是互斥体包装器,为在作用域块期间占有互斥提供便利 RAII 风格机制。
创建 lock_guard 对象时,它试图接收给定互斥的所有权。控制离开创建 lock_guard 对象的作用域时,销毁 lock_guard 并释放互斥。
lock_guard 类不可复制。
成员类型 mutex_type Mutex成员函数 成员函数 (构造函数) 构造 lock_guard ,可选地锁定给定的互斥(公开成员函数) (析构函数) 析构 lock_guard 对象,解锁底层互斥(公开成员函数) operator=[被删除] 不可复制赋值(公开成员函数)
#include <thread> #include <mutex> #include <iostream> int g_i = 0; std::mutex g_i_mutex; // 保护 g_i void safe_increment() { std::lock_guard<std::mutex> lock(g_i_mutex); ++g_i; std::cout << std::this_thread::get_id() << ": " << g_i << '\n'; // g_i_mutex 在锁离开作用域时自动释放 } int main() { std::cout << "main: " << g_i << '\n'; std::thread t1(safe_increment); std::thread t2(safe_increment); t1.join(); t2.join(); std::cout << "main: " << g_i << '\n'; }
可能的输出: main: 0 140641306900224: 1 140641298507520: 2 main: 2
类 scoped_lock 是提供便利 RAII 风格机制的互斥包装器,它在作用域块的存在期间占有一或多个互斥。
创建 scoped_lock 对象时,它试图取得给定互斥的所有权。控制离开创建 scoped_lock 对象的作用域时,析构 scoped_lock 并释放互斥。若给出数个互斥,则使用免死锁算法,如同以 std::lock 。
scoped_lock 类不可复制。
成员类型 mutex_type (若 sizeof...(MutexTypes)==1) Mutex , MutexTypes... 中的单独类型 成员函数 (构造函数) 构造 scoped_lock ,可选地锁定给定的互斥(公开成员函数) (析构函数) 析构 scoped_lock 对象,解锁底层互斥(公开成员函数) operator=[被删除] 不可复制(公开成员函数)
#include <mutex> #include <thread> #include <iostream> #include <vector> #include <functional> #include <chrono> #include <string> struct Employee { Employee(std::string id) : id(id) {} std::string id; std::vector<std::string> lunch_partners; std::mutex m; std::string output() const { std::string ret = "Employee " + id + " has lunch partners: "; for (const auto &partner : lunch_partners) ret += partner + " "; return ret; } }; void send_mail(Employee &, Employee &) { // 模拟耗时的发信操作 std::this_thread::sleep_for(std::chrono::seconds(1)); } void assign_lunch_partner(Employee &e1, Employee &e2) { static std::mutex io_mutex; { std::lock_guard<std::mutex> lk(io_mutex); std::cout << e1.id << " and " << e2.id << " are waiting for locks" << std::endl; } { // 用 std::scoped_lock 取得二个锁,而无需担心 // 其他对 assign_lunch_partner 的调用死锁我们 // 而且它亦提供便利的 RAII 风格机制 std::scoped_lock lock(e1.m, e2.m); // 等价代码 1 (用 std::lock 和 std::lock_guard ) // std::lock(e1.m, e2.m); // std::lock_guard<std::mutex> lk1(e1.m, std::adopt_lock); // std::lock_guard<std::mutex> lk2(e2.m, std::adopt_lock); // 等价代码 2 (若需要 unique_lock ,例如对于条件变量) // std::unique_lock<std::mutex> lk1(e1.m, std::defer_lock); // std::unique_lock<std::mutex> lk2(e2.m, std::defer_lock); // std::lock(lk1, lk2); { std::lock_guard<std::mutex> lk(io_mutex); std::cout << e1.id << " and " << e2.id << " got locks" << std::endl; } e1.lunch_partners.push_back(e2.id); e2.lunch_partners.push_back(e1.id); } send_mail(e1, e2); send_mail(e2, e1); } int main() { Employee alice("alice"), bob("bob"), christina("christina"), dave("dave"); // 在并行线程中指派,因为就午餐指派发邮件消耗很长时间 std::vector<std::thread> threads; threads.emplace_back(assign_lunch_partner, std::ref(alice), std::ref(bob)); threads.emplace_back(assign_lunch_partner, std::ref(christina), std::ref(bob)); threads.emplace_back(assign_lunch_partner, std::ref(christina), std::ref(alice)); threads.emplace_back(assign_lunch_partner, std::ref(dave), std::ref(bob)); for (auto &thread : threads) thread.join(); std::cout << alice.output() << '\n' << bob.output() << '\n' << christina.output() << '\n' << dave.output() << '\n'; }
可能的输出:
alice and bob are waiting for locks alice and bob got locks christina and bob are waiting for locks christina and alice are waiting for locks dave and bob are waiting for locks dave and bob got locks christina and alice got locks christina and bob got locks Employee alice has lunch partners: bob christina Employee bob has lunch partners: alice dave christina Employee christina has lunch partners: alice bob Employee dave has lunch partners: bob
类 unique_lock 是通用互斥包装器,允许延迟锁定、锁定的有时限尝试、递归锁定、所有权转移和与条件变量一同使用。
类 unique_lock 可移动,但不可复制——它满足可移动构造 (MoveConstructible) 和可移动赋值 (MoveAssignable) 但不满足可复制构造 (CopyConstructible) 或可复制赋值 (CopyAssignable) 。
类 unique_lock 满足基本可锁定 (BasicLockable) 要求。若 Mutex 满足可锁定 (Lockable) 要求,则 unique_lock 亦满足可锁定 (Lockable) 要求(例如:能用于 std::lock ) ;若 Mutex 满足可定时锁定 (TimedLockable) 要求,则 unique_lock 亦满足可定时锁定 (TimedLockable) 要求。
成员类型 mutex_type Mutex 成员函数 (构造函数) 构造 unique_lock ,可选地锁定提供的互斥(公开成员函数) (析构函数) 若占有关联互斥,则解锁之(公开成员函数) operator= 若占有则解锁互斥,并取得另一者的所有权(公开成员函数) 锁定 lock 锁定关联互斥(公开成员函数) try_lock 尝试锁定关联互斥,若互斥不可用则返回(公开成员函数) try_lock_for 试图锁定关联的可定时锁定 (TimedLockable) 互斥,若互斥在给定时长中不可用则返回(公开成员函数) try_lock_until 尝试锁定关联可定时锁定 (TimedLockable) 互斥,若抵达指定时间点互斥仍不可用则返回(公开成员函数) unlock 解锁关联互斥(公开成员函数) 修改器 swap 与另一 std::unique_lock 交换状态(公开成员函数) release 将关联互斥解关联而不解锁它(公开成员函数) 观察器 mutex 返回指向关联互斥的指针(公开成员函数) owns_lock 测试锁是否占有其关联互斥(公开成员函数) operator bool 测试锁是否占有其关联互斥(公开成员函数)非成员函数 std::swap(std::unique_lock) (C++11) std::swap 对 unique_lock 的特化(函数模板)
#include <mutex> #include <thread> #include <chrono> 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<std::mutex> lock1(from.m, std::defer_lock); std::unique_lock<std::mutex> 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(); }
类 shared_lock 是通用共享互斥所有权包装器,允许延迟锁定、定时锁定和锁所有权的转移。锁定 shared_lock ,会以共享模式锁定关联的共享互斥( std::unique_lock 可用于以排他性模式锁定)。
shared_lock 类可移动,但不可复制——它满足可移动构造 (MoveConstructible) 与可移动赋值 (MoveAssignable) 的要求,但不满足可复制构造 (CopyConstructible) 或可复制赋值 (CopyAssignable) 。
shared_lock 符合可锁定 (Lockable) 要求。若 Mutex 符合可共享定时锁定 (SharedTimedLockable) 要求,则 shared_lock 亦符合 可定时锁定 (TimedLockable) 要求。
为以共享所有权模式等待于共享互斥,可使用 std::condition_variable_any ( std::condition_variable 要求 std::unique_lock 故而只能以唯一所有权模式等待)。
成员类型 mutex_type Mutex 成员函数 (构造函数) 构造 shared_lock ,可选地锁定提供的互斥(公开成员函数) (析构函数) 解锁关联的互斥(公开成员函数) operator= 若占有则解锁互斥,然后获得对方的所有权(公开成员函数) 共享锁定 lock 锁定关联的互斥(公开成员函数) try_lock 尝试锁定关联的互斥(公开成员函数) try_lock_for 尝试锁定关联的互斥,以指定时长(公开成员函数) try_lock_until 尝试锁定关联的互斥,直至指定的时间点(公开成员函数) unlock 解锁关联的互斥(公开成员函数) 修改器 swap 与另一 shared_lock 交换数据成员(公开成员函数) release 解除关联 mutex 而不解锁(公开成员函数) 观察器 mutex 返回指向关联的互斥的指针(公开成员函数) owns_lock 测试锁是否占有其关联的互斥(公开成员函数) operator bool 测试锁是否占有其关联的互斥(公开成员函数) 非成员函数 std::swap(std::shared_lock)(C++14) std::swap 对 shared_lock 的特化(函数模板)
锁定给定的可锁定 (Lockable) 对象 lock1 、 lock2 、 … 、 lockn ,用免死锁算法避免死锁。
以对 lock 、 try_lock 和 unlock 的未指定系列调用锁定对象。若调用 lock 或 unlock 导致异常,则在重抛前对任何已锁的对象调用 unlock 。
#include <mutex> #include <thread> #include <iostream> #include <vector> #include <functional> #include <chrono> #include <string> struct Employee { Employee(std::string id) : id(id) {} std::string id; std::vector<std::string> lunch_partners; std::mutex m; std::string output() const { std::string ret = "Employee " + id + " has lunch partners: "; for( const auto& partner : lunch_partners ) ret += partner + " "; return ret; } }; void send_mail(Employee &, Employee &) { // 模拟耗时的发信操作 std::this_thread::sleep_for(std::chrono::seconds(1)); } void assign_lunch_partner(Employee &e1, Employee &e2) { static std::mutex io_mutex; { std::lock_guard<std::mutex> lk(io_mutex); std::cout << e1.id << " and " << e2.id << " are waiting for locks" << std::endl; } // 用 std::lock 获得二个锁,而不担心对 assign_lunch_partner 的其他调用会死锁我们 { std::lock(e1.m, e2.m); std::lock_guard<std::mutex> lk1(e1.m, std::adopt_lock); std::lock_guard<std::mutex> lk2(e2.m, std::adopt_lock); // 等价代码(若需要 unique_locks ,例如对于条件变量) // std::unique_lock<std::mutex> lk1(e1.m, std::defer_lock); // std::unique_lock<std::mutex> lk2(e2.m, std::defer_lock); // std::lock(lk1, lk2); // C++17 中可用的较优解法 // std::scoped_lock lk(e1.m, e2.m); { std::lock_guard<std::mutex> lk(io_mutex); std::cout << e1.id << " and " << e2.id << " got locks" << std::endl; } e1.lunch_partners.push_back(e2.id); e2.lunch_partners.push_back(e1.id); } send_mail(e1, e2); send_mail(e2, e1); } int main() { Employee alice("alice"), bob("bob"), christina("christina"), dave("dave"); // 在平行线程指派,因为发邮件给用户告知午餐指派,会消耗长时间 std::vector<std::thread> threads; threads.emplace_back(assign_lunch_partner, std::ref(alice), std::ref(bob)); threads.emplace_back(assign_lunch_partner, std::ref(christina), std::ref(bob)); threads.emplace_back(assign_lunch_partner, std::ref(christina), std::ref(alice)); threads.emplace_back(assign_lunch_partner, std::ref(dave), std::ref(bob)); for (auto &thread : threads) thread.join(); std::cout << alice.output() << '\n' << bob.output() << '\n' << christina.output() << '\n' << dave.output() << '\n'; }
可能的输出:
alice and bob are waiting for locks alice and bob got locks christina and bob are waiting for locks christina and bob got locks christina and alice are waiting for locks christina and alice got locks dave and bob are waiting for locks dave and bob got locks Employee alice has lunch partners: bob christina Employee bob has lunch partners: alice christina dave Employee christina has lunch partners: bob alice Employee dave has lunch partners: bob
std::lock_guard的std::adopt_lock参数
unique_lock的第二个参数std::adopt_lock:
std::try_to_lock:
std::defer_lock:
函数模板,该函数的第一个参数为标记,第二个参数是一个函数名。
功能:能够保证函数只被调用一次。具备互斥量的能力,而且比互斥量消耗的资源更少,更高效。
call_once()需要与一个标记结合使用,这个标记为std::once_flag;其实once_flag是一个结构,call_once()就是通过标记来决定函数是否执行,调用成功后,就把标记设置为一种已调用状态。
#include <iostream> #include <thread> #include <mutex> std::once_flag flag1, flag2; void simple_do_once() { std::call_once(flag1, []() { std::cout << "Simple example: called once\n"; }); } void may_throw_function(bool do_throw) { if (do_throw) { std::cout << "throw: call_once will retry\n"; // throw std::exception(); } std::cout << "Didn't throw, call_once will not attempt again\n"; } void do_once(bool do_throw) { try { std::call_once(flag2, may_throw_function, do_throw); } catch (...) { } } int main() { std::thread st1(simple_do_once); std::thread st2(simple_do_once); std::thread st3(simple_do_once); st1.join(); st2.join(); st3.join(); std::thread t1(do_once, true); std::thread t2(do_once, false); std::thread t3(do_once, true); std::thread t4(do_once, true); t1.join(); t2.join(); t3.join(); t4.join(); }
可能会是:
Simple example: called once throw: call_once will retry Didn't throw, call_once will not attempt again