当前位置: 首页 > news >正文

[C/C++线程安全]_[中级]_[多线程如何使用共享锁提升性能]

场景

  1. C/C++多线程程序里,经常会用到对共享变量进行读写,最容易想到的是加上锁进行读写。假设当有多个线程对共享变量进行读操作时,该业务逻辑的耗时需要至少2s来进行处理,那么当一个读线程进行锁定时,其他读线程只能等待2s才会被唤醒。因为读共享变量是线程安全的,有没有方法在读的时候不需要排他锁占用?

说明

  1. C/C++14开始,提供了一个std::shared_lock共享锁,需要传入一个共享互斥量std::shared_mutex来进行读写锁操作。这个共享锁提升了多线程读共享变量的性能。
static shared_mutex rwMutex;
static string gData;...
shared_lock<shared_mutex> lock(rwMutex);
std::string str(gData);
  1. std::shared_lock的作用就是在一个线程获取到共享锁的时候,在没释放前,其他读线程可以继续获取这个共享锁,而写线程想要获取这个mutex的排他锁定,只能进入等待。
unique_lock<shared_mutex> lock(rwMutex);
gData.append(to_string(gAccumulate++)).append(":");
  1. std::shared_mutex可以作为std::unique_lock锁的参数进行排他锁定。 如果有多个写线程,那么其他写线程只能等待第一个写线程操作结束才会被唤醒去获取排他锁。

  2. C++并没有编译时的线程安全检查,默认不禁止数据竞争,如果发生数据竞争时,结果是未定义的,也就是可能导致系统崩溃。 要保证没有数据竞争,只能靠严格的执行库的使用规则,这点即使高级程序员也容易偶尔犯错。希望在未来的标准里可以加上之类编译时检查。

  3. 还有一点就是尽量在加锁操作时使用RAII的特性,比如使用类shared_lockunique_lock来创建局部变量。这样锁定和解锁会成对执行,而不是调用shared_mutexlock(),lock_shared()unlock(),unlock_shared()方法。

例子

  1. 这里写了两个方法来对比了共享锁和排他锁的性能。很明显的,共享锁大大的提高了性能。
// test-shared-lock.cpp : 此文件包含 "main" 函数。程序执行将在此处开始并结束。
//#include <iostream>
#include <shared_mutex>
#include <thread>
#include <string>
#include <sstream>
#include <vector>
#include <mutex>
#include <chrono>
#include <condition_variable>using namespace std;// 唯一锁
static mutex uMutex;// 读写锁
static shared_mutex rwMutex;
static string gData;static mutex rMutex;
static stringstream rData;
static condition_variable rCond;static int gAccumulate;static chrono::steady_clock::time_point Cpp11FuncBegin()
{return chrono::steady_clock::now();
}static double Cpp11FuncEnd(chrono::steady_clock::time_point t)
{auto elapse = chrono::steady_clock::now() - t;chrono::duration<double,milli> mi = elapse;return mi.count();
}template<class T,class S>
void FuncReader(int index,S& s)
{{std::unique_lock<mutex> lock(rMutex);rCond.wait(lock);}auto tPoint = Cpp11FuncBegin();//shared_lock<shared_mutex> lock(rwMutex);T lock(s);std::string str(gData);// -- 增加这个是为了看共享锁是否能同时进入; 如果能同时进入那么执行时间大概是2000ms左右.std::this_thread::sleep_for(std::chrono::milliseconds(2000)); auto result = Cpp11FuncEnd(tPoint);std::unique_lock<mutex> myLock(rMutex);rData << "index: " << index << " Read: " << str << " Duration: " << result << "ms \n";}static void FuncWriter(int index)
{{std::unique_lock<mutex> lock(rMutex);rCond.wait(lock);}unique_lock<shared_mutex> lock(rwMutex);gData.append(to_string(gAccumulate++)).append(":");std::unique_lock<mutex> myLock(rMutex);rData << "index" << index << " Write: " << gData << "\n";}// -- 读写共享锁
static void TestReadWriteSharedData()
{cout << "======== TestReadWriteSharedData ==========" << endl;rData.clear();gData.clear();vector<thread> tasks;int i = 0;for (; i < 8; ++i) tasks.emplace_back(FuncReader<shared_lock<shared_mutex>,shared_mutex>,i,std::ref(rwMutex));for(; i < 10; ++i)tasks.emplace_back(FuncWriter,i);rCond.notify_all();for (auto& one : tasks)one.join();auto str = rData.str();cout << str << endl;
}// -- 普通的非共享锁
static void TestUniqueLock()
{cout << "======== TestUniqueLock ==========" << endl;rData.clear();rData.str("");gData = "0;";vector<thread> tasks;int i = 0;for (; i < 4; ++i) tasks.emplace_back(FuncReader<unique_lock<mutex>,mutex>,i,std::ref(uMutex));rCond.notify_all();for (auto& one : tasks)one.join();auto str = rData.str();cout << str << endl;
}int main()
{cout << "Hello World!\n";TestReadWriteSharedData();TestUniqueLock();
}

输出

Hello World!
======== TestReadWriteSharedData ==========
index9 Write: 0:
index8 Write: 0:1:
index: 1 Read: 0:1: Duration: 2005.57ms
index: 6 Read: 0:1: Duration: 2005.72ms
index: 0 Read: 0:1: Duration: 2005.57ms
index: 7 Read: 0:1: Duration: 2005.57ms
index: 2 Read: 0:1: Duration: 2005.6ms
index: 3 Read: 0:1: Duration: 2005.63ms
index: 4 Read: 0:1: Duration: 2005.64ms
index: 5 Read: 0:1: Duration: 2005.67ms======== TestUniqueLock ==========
index: 3 Read: 0; Duration: 2005.35ms
index: 2 Read: 0; Duration: 4012.66ms
index: 0 Read: 0; Duration: 6021.45ms
index: 1 Read: 0; Duration: 8031.52ms

参考

  1. std::shared_lock

  2. std::shared_mutex

  3. 多线程访问修改集合vector会冲突的两个解决方案

  4. C++实现synchronized方式的对象锁

  5. C++11语言特性和标准库

  6. C++14语言特性和标准库

http://www.lryc.cn/news/614807.html

相关文章:

  • Meta AI水印计划的致命缺陷——IEEE Spectrum深度文献精读
  • 第4章 程序段的反复执行4.2while语句P128练习题(题及答案)
  • ppt 生成视频的 ai 大模型全面解析
  • (talk)西安大模型开发者talk
  • vue+flask大模型写诗诗词推荐与可视化系统
  • 浏览器面试题及详细答案 88道(01-11)
  • 项目一系列-第4章 在线接口文档 代码模板改造
  • AJAX与axios框架
  • Netty-Rest搭建笔记
  • 系统集成项目管理工程师【第十一章 规划过程组】规划成本管理、成本估算、制定预算和规划质量管理篇
  • 轻松实现浏览器自动化——AI浏览器自动化框架Stagehand
  • 【华为机试】63. 不同路径 II
  • C++简单项目跟练【通讯录管理系统000】
  • 数据集: TSPLIB旅行商问题-对称TSP数据集
  • 宁商平台税务升级之路:合规为纲,服务为本
  • 五、SpringBoot工程打包与运行
  • 解决 MinIO 上传文件时报 S3 API Requests must be made to API port错误
  • Sklearn 机器学习 数据降维PCA 使用PCA算法
  • Java 之 设计模式
  • Python day38
  • SVM算法实战应用
  • 【感知机】感知机(perceptron)学习算法例题及详解
  • 政治社会时间线
  • 为什么输入 URL 后会显示页面?HTTP 协议的 “幕后操作”
  • JDK、eclipse的安装,配置JDK、Tomcat并使用eclipse创建项目
  • Cursor CLI 来了,准备 Build anything
  • latex基础
  • Vue 路由跳转
  • Redis数据组织方式
  • 第39周——训练自己的数据集