C++ 通用锁管理
lock_guard
类 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
类 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 是通用互斥包装器,允许延迟锁定、锁定的有时限尝试、递归锁定、所有权转移和与条件变量一同使用。
类 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 是通用共享互斥所有权包装器,允许延迟锁定、定时锁定和锁所有权的转移。锁定 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 的特化(函数模板)
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::defer_lock, std::try_to_lock, std::adopt_lock
std::lock_guard的std::adopt_lock参数
- 加入adopt_lock后,在调用lock_guard的构造函数时,不再进行lock();
- adopt_guard为结构体对象,起一个标记作用,表示这个互斥量已经lock(),不需要在lock()。
unique_lock的第二个参数std::adopt_lock:
- 表示这个互斥量已经被lock(),即不需要在构造函数中lock这个互斥量了。
- 前提:必须提前lock
std::try_to_lock:
- 尝试用mutex的lock()去锁定这个mutex,但如果没有锁定成功,会立即返回,不会阻塞在那里;
- 使用try_to_lock的原因是防止其他的线程锁定mutex太长时间,导致本线程一直阻塞在lock这个地方
- 前提:不能提前lock();
- owns_locks()方法判断是否拿到锁,如拿到返回true
std::defer_lock:
- 如果没有第二个参数就对mutex进行加锁,加上defer_lock是始化了一个没有加锁的mutex
- 不给它加锁的目的是以后可以调用unique_lock的一些方法
- 前提:不能提前lock
call_once
函数模板,该函数的第一个参数为标记,第二个参数是一个函数名。
功能:能够保证函数只被调用一次。具备互斥量的能力,而且比互斥量消耗的资源更少,更高效。
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
C++ 通用锁管理的更多相关文章
- 解析大型.NET ERP系统 通用附件管理功能
大型系统具备一个通用的附件管理功能,对于单据中无法清晰表达的字段,用一个附件图片或附件文档表示是最好的方法了.比如物料清单附加一张CAD图纸,销售订单评审功能中附加客户的各种表格,通用附件功能对系统起 ...
- winform快速开发平台 -> 通用权限管理之动态菜单
这几个月一直忙APP的项目,没来得及更新项目,想想该抽出时间整理一下开发思路,跟大家分享,同时也希望得到宝贵的建议. 先说一下我们的权限管理的的设计思路,首先一个企业信息化管理系统一定会用到权限管理, ...
- 基于吉日嘎拉的通用权限管理Webform版老界面bug修复
虽然弄了新界面<基于吉日嘎底层架构的通用权限管理Web端UI更新:参考DTcms后台界面>,但老界面的一点菜单显示的问题还是让我这种强迫症揪心,终于今晚可以美美的睡觉了. 老代码用了Ses ...
- 基于吉日嘎底层架构的通用权限管理Web端UI更新:参考DTcms后台界面
经一周的研究学习,看了国内的H+.HUI等,国外的PaperDashboardPro.Make.Metronic BootStrap等,最终选定用一个轻量的,适合中国人的,来自DTcms的后台管理UI ...
- 【原创】C#通用权限管理-程序安全检查,这些你一定要考虑到位
接触通用权限已经一年,现在使用已经很熟练,分享通用权限管理下面的一些好的开发思想. 安全漏洞对于一个小项目来说,可能不是特别的重视,对于一个大项目来说,这是特别重要需要注意的,特别是在项目开发中的就要 ...
- EASYUI+MVC4通用权限管理平台--前言
经过多年的管理信息系统的开发工作,吸取了工作中遇到的一些问题,经过自己的总结,形成了一套比较完整的管理信息系统的通用权限管理基础开发平台. 在软件的开发过程中我们首先需要解决的是UI问题,其次是浏览器 ...
- EASYUI+MVC4通用权限管理平台
通用权限案例平台在经过几年的实际项目使用,并取得了不错的用户好评.在平台开发完成后,特抽空总结一下平台知识,请各位在以后的时间里,关注博客的更新. 1.EASYUI+MVC4通用权限管理平台--前言 ...
- C#通用权限管理-程序安全检查,这些你一定要考虑到位
接触通用权限已经一年,现在使用已经很熟练,分享通用权限管理下面的一些好的开发思想. 安全漏洞对于一个小项目来说,可能不是特别的重视,对于一个大项目来说,这是特别重要需要注意的,特别是在项目开发中的就要 ...
- Winform开发框架之通用附件管理模块 --SNF快速开发平台3.3-Spring.Net.Framework
最近项目太多都没有时间写文章了,实际项目需求一,CS端和windows平板都需要附件上传管理功能.以前做的都是BS的附件管理和上传功能.本来计划在Winform上嵌套一个浏览器直接用bs的附件上传功能 ...
- (转)EASYUI+MVC4通用权限管理平台
原文地址:http://www.cnblogs.com/hn731/archive/2013/07/15/3190947.html 通用权限案例平台在经过几年的实际项目使用,并取得了不错的用户好评.在 ...
随机推荐
- 玩转SpringBoot:动态排除Starter配置,轻松部署
引言 在软件开发中,进行本地单元测试是一项常规且必要的任务.然而,在进行单元测试时,有时需要启动一些中间件服务,如Kafka.Elasticjob等.举例来说,我曾经遇到过一个问题:项目中使用了Red ...
- 麒麟系统开发笔记(八):在国产麒麟系统上使用linuxdeployqt发布qt程序
前言 在ubuntu上发布qt程序相对还好,使用脚本,但是在麒麟上发布的时候,因为银河麒麟等不同版本,使用脚本就不太兼容,同时为了实现直接点击应用可以启动应用的效果,使用linuxdeployqt ...
- 现代 CSS 解决方案:accent-color 强调色
accent-color 是从 Chrome 93 开始被得到支持的一个不算太新属性.之前一直没有好好介绍一下这个属性.直到最近在给一些系统整体切换主题色的时候,更深入的了解了一下这个属性. 简单而言 ...
- Linux开端---Centos
Linux-Centos 虚拟化所需工具:https://pan.baidu.com/s/1643-kYcx9oPGnGEZM1pLOw?pwd=g0v5 提取码:g0v5 问题解决 正常注册网络适配 ...
- 问题:django.template.exceptions.TemplateSyntaxError: 'staticfiles' is not a registered tag library. Must be one of: admin_list admin_modify admin_urls cache i18n l10n log rest_framework static tz
django使用swagger自动生成API文档时,报错 解决方法 在settings.py里面配置一下以下代码 'libraries': { 'staticfiles': 'django.templ ...
- MySQL 数据库死锁排查
死锁排查方法 查看进程状态 show processlist; 查看行锁的状态 show status like 'InnoDB_row_lock%'; 查询是否有死锁 show engine inn ...
- 【Azure Key Vault】在Alteryx中使用Azure Key Vault存储账号和密码并实现无交互登录
问题描述: 需要在Alteryx中使用Azure Key Vault, Alteryx 能将数据发布到 Tableau,需要输入账号和密码,使用Azure Key Vault来替换这个输入账号和密码的 ...
- 【Azure 服务总线】Azure.Messaging.ServiceBus 多次发送消息报超时错误,是否可以配置重新发送?是否有内置重试机制?
问题描述 使用 Azure Service Bus,提供应用程序之间松耦合的消息交换,但是有时候发送消息多次出现超时错误. A connection attempt failed because th ...
- Java package(包) +import 关键字(1)
Java 包(package) 为了更好地组织类,Java 提供了包机制,用于区别类名的命名空间. 包的作用 1.把功能相似或相关的类或接口组织在同一个包中,方便类的查找和使用. 2.如同文件夹一样, ...
- 那些年,我的Mysql学习之旅(学习笔记持续整理更新中)
MySql海量数据存储与优化 一.Mysql架构原理和存储机制 1.体系结构 2.查询缓存 3.存储引擎 存储引擎的分类 innodb:支持事务,具有支持回滚,提交,崩溃恢复等功能,事务安全 myis ...