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

STL,智能指针和线程安全自选锁读者写者问题

目录

STL中的容器是否是线程安全的?

智能指针是否是线程安全的?

其他常见的各种锁

读者写者问题(了解)

读写锁

读写锁接口

读写锁伪代码


STL中的容器是否是线程安全的?

不是.

原因是, STL 的设计初衷是将性能挖掘到极致, 而一旦涉及到加锁保证线程安全, 会对性能造成巨大的影响. 而且对于不同的容器, 加锁方式的不同, 性能可能也不同(例如hash表的锁表和锁桶). 因此 STL 默认不是线程安全. 如果需要在多线程环境下使用, 往往需要调用者自行保证线程安全.

智能指针是否是线程安全的?

对于 unique_ptr, 由于只是在当前代码块范围内生效, 因此不涉及线程安全问题.

对于shared_ptr, 多个对象需要共用一个引用计数变量, 所以会存在线程安全问题. 但是标准库实现的时候考虑到了这个问题, 基于原子操作(CAS)的方式保证 shared_ptr 能够高效, 原子的操作引用计数.

线程安全share_ptr的实现如下:

template<class T>
class shared_ptr
{
public:// RAIIshared_ptr(T* ptr = nullptr):_ptr(ptr), _pcount(new atomic<int>(1)){}template<class D>shared_ptr(T* ptr, D del): _ptr(ptr), _pcount(new atomic<int>(1)), _del(del){}// function<void(T*)> _del;void release(){if (--(*_pcount) == 0){//cout << "delete->" << _ptr << endl;//delete _ptr;_del(_ptr);delete _pcount;}}~shared_ptr(){release();}shared_ptr(const shared_ptr<T>& sp):_ptr(sp._ptr), _pcount(sp._pcount){++(*_pcount);}// sp1 = sp3shared_ptr<T>& operator=(const shared_ptr<T>& sp){if (_ptr != sp._ptr){release(); //释放sp1原先的地址_ptr = sp._ptr;_pcount = sp._pcount;++(*_pcount);}return *this;}// 像指针一样T& operator*(){return *_ptr;}T* operator->(){return _ptr;}int use_count() const{return *_pcount;}T* get() const{return _ptr;}private:T* _ptr;atomic<int>* _pcount;function<void(T*)> _del = [](T* ptr) {delete ptr; };
};

虽然share_ptr本身是线程安全的,但是它里面的资源并不是线程安全的

void test_shared_ptr()
{size_t n1 = 10000;size_t n2 = 10000;mutex mtx;shared_ptr<double> sp(new double(1.1));atomic<size_t> x = 0;thread t1([&]() {for (size_t i = 0; i < n1; i++){shared_ptr<double> copy1(sp);++(*copy1);shared_ptr<double> copy2(sp);}});thread t2([&]() {for (size_t i = 0; i < n2; i++){shared_ptr<double> copy1(sp);++(*copy1);shared_ptr<double> copy2(sp);}});t1.join();t2.join();cout << sp.use_count() << endl;cout << *sp << endl;
}

运行结果:

所以当我们访问SmartPtr中的资源时,需要进行加锁保护

其他常见的各种锁

悲观锁:在每次取数据时,总是担心数据会被其他线程修改,所以会在取数据前先加锁(读锁,写锁,行 锁等),当其他线程想要访问数据时,被阻塞挂起。

乐观锁:每次取数据时候,总是乐观的认为数据不会被其他线程修改,因此不上锁。但是在更新数据前, 会判断其他数据在更新前有没有对数据进行修改。主要采用两种方式:版本号机制和CAS操作。 CAS操作:当需要更新数据时,判断当前内存值和之前取得的值是否相等。如果相等则用新值更新。若不 等则失败,失败则重试,一般是一个自旋的过程,即不断重试。

自旋锁:申请锁失败之后不会返回,而是一直询问锁资源是否就绪,但是访问临界区时间长的话非常占用CPU资源,自旋锁适用于访问临界区时间短的场景,而访问临界区时间长的场景,外面一般使用挂起等待锁

读者写者问题(了解)

读写锁

在编写多线程的时候,有一种情况是十分常见的。那就是,有些公共数据修改的机会比较少。相比较改写,它们读的机会反而高的多。通常而言,在读的过程中,往往伴随着查找的操作,中间耗时很长。给这种代码段加锁,会极大地 降低我们程序的效率。那么有没有一种方法,可以专门处理这种多读少写的情况呢? 有,那就是读写锁。

注意:写独占,读共享,读锁优先级高

读写锁接口

int pthread_rwlockattr_setkind_np(pthread_rwlockattr_t *attr, int pref); 

功能:设置某个读写锁属性

函数参数说明:

attr:读写锁属性

pref:pref 共有 3 种选择

  • PTHREAD_RWLOCK_PREFER_READER_NP (默认设置) 读者优先,可能会导致写者饥饿情况
  • PTHREAD_RWLOCK_PREFER_WRITER_NP 写者优先,目前有 BUG,导致表现行为和PTHREAD_RWLOCK_PREFER_READER_NP 一致
  • PTHREAD_RWLOCK_PREFER_WRITER_NONRECURSIVE_NP 写者优先,但写者不能递归加锁

注意:读写锁的适用场景就是读多写少的情况

初始化

int pthread_rwlock_init(pthread_rwlock_t *restrict rwlock,const pthread_rwlockattr_t 
*restrict attr); 

第一个参数是读写锁变量,第二个参数是读写锁的属性一般不需要管

销毁

int pthread_rwlock_destroy(pthread_rwlock_t *rwlock);

加锁和解锁

int pthread_rwlock_rdlock(pthread_rwlock_t *rwlock);  //读锁
int pthread_rwlock_wrlock(pthread_rwlock_t *rwlock);  //写锁int pthread_rwlock_unlock(pthread_rwlock_t *rwlock);  //解锁

读写锁伪代码

与生产者消费者模型一样,遵循321原则

3种关系:写者和写者(互斥), 读者和读者(共享), 读者和写者(互斥,同步,读者优先)

2个角色:写者和读者

1个交易场所:数据交换的地点

读者优先的伪代码

读者伪代码:

int reader_count = 0; //允许多个读者同时进行读操作,适用引用计数的原理进行维护//读者的加锁&&解锁
lock(&rlock);
reader_count++;
if(reader_count == 1) lock(&wlock); //只要有一个读者进行读操作,就让写者停止写入
unlock(&rlock);//读取操作//写者的加锁&&解锁
lock(&rlock);
reader_count--;
if(reader_count == 0) unlock(&wlock); //读者全部退出之后,为写者进行解锁
unlock(&rlock);

写者伪代码:

lock(&wlock);//写入操作unlock(&wlock);
http://www.xdnf.cn/news/445231.html

相关文章:

  • 蓝桥杯13届国B 完全日期
  • 【vue】生命周期钩子使用
  • 【行为型之访问者模式】游戏开发实战——Unity灵活数据操作与跨系统交互的架构秘诀
  • 关于Python 实现接口安全防护:限流、熔断降级与认证授权的深度实践
  • 2024年业绩增速大幅回退,泸州老窖未能“重回前三”
  • 使用Rust开发的智能助手系统,支持多模型、知识库和MCP
  • Go 语言 sqlx 库使用:对 MySQL 增删改查
  • Spring Boot requestBody postman
  • 人机环境体系的自主决策与机器系统的自主决策不同
  • 第二章:CSS秘典 · 色彩与布局的力量
  • 时源芯微| KY键盘接口静电浪涌防护方案
  • 【免杀】C2免杀技术(三)shellcode加密
  • ​Android学习总结之handler中源码解析和场景回答
  • scikit-learn在无监督学习算法的应用
  • 【愚公系列】《Manus极简入门》038-数字孪生设计师:“虚实映射师”
  • kaggle薅羊毛
  • 计算机操作系统(七)详细讲解进程的组成与特性,状态与转换
  • ESP32WIFI工具加透传
  • 生命之舞:创建,终止与等待,Linux进程控制的交响乐章
  • Jmeter元件 CSV Data Set Config详解
  • (1-4)Java Object类、Final、注解、设计模式、抽象类、接口、内部类
  • Doris与ClickHouse深度比较
  • 语音合成之十四 文本转语音(TTS)开源数据集
  • 互联网大厂Java求职面试:优惠券服务架构设计与AI增强实践-6
  • 使用IDEA创建Maven版本的web项目以及lombok的使用
  • 玛哈特矫平机:金属板材加工中的“平整大师”
  • 解读RTOS 第七篇 · 驱动框架与中间件集成
  • Milvus 全面解析
  • 非异步信号安全函数
  • The 2022 ICPC Asia Xian Regional Contest(E,L)题解