Linux多线程服务端编程:线程安全的对象管理

news2024/11/17 13:35:19

1. 前置知识

1.1 __builtin_expect

1.1.1 使用

__builtin_expect提供给编译器分支预测优化信息,其含义为 exp 大概率为 c,其返回值为 exp 的值;

long __builtin_expect(long exp, long c)
// 下述表明该分支大概率不会执行
if (__builtin_expect(t_cachedTid == 0, 0))
{
  func();
}
// C++20 正式将其变为关键字,之前的宏如下
// 两次取非,可以保证最后一定为 bool 值
#define likely(x)     __builtin_expect(!!(x), 1)
#define unlikely(x)   __builtin_expect(!!(x), 0)

使用这个优化手段时,必须要保证概率差别真大很多(默认要有 90% 的把握),在 muduo 库中用在缓存线程 tid 场景;

1.1.2 原理
// -fprofile-arcs 必须要加上才会在汇编代码中体现出来区分
gcc -fprofile-arcs -O2 myexpect.cpp -o mexp
objdump -S mexp

__builtin_expect 在汇编代码中体现是,把概率更大的分支与之后的代码直接放在一起,概率小的分支需要进行跳转;

if (likely(a == 2))
   a++;
else
   a--;

如下图红线为概率小的分支,蓝线为概率大的分支;(这样的好处是 CPU 流水线可以大概率顺利执行,从而提高效率)

在这里插入图片描述

1.1.3 参考

https://kernelnewbies.org/FAQ/LikelyUnlikely
https://gcc.gnu.org/onlinedocs/gcc/Other-Builtins.html

1.2 __thread

(1)GCC 内置的线程局部存储,类似全局变量,但有所不同;

每个线程都拥有该变量的一份拷贝,且互不干扰。
线程局部存储中的变量将一直存在,直至线程终止,当线程终止时会自动释放这一存储;
只能存储一些小变量(基本为内置类型);

(2)C++11 新引入了 thread_local 关键字,其与之作用相同;

1.3 const 成员函数

(1)一般情况下 const 对象调用 const 成员函数,普通对象调用非 const 成员函数
const 对象不可用调用非 const 成员函数。普通对象可以调用 const 成员函数

(2)const 成员函数中可以修改静态数据成员,不可以修改非静态成员(此处的理解是,从语义方面来讲,静态成员变量类似于全局变量,不属于某个对象,因此不算是对对象进行了修改。)

(3)对于声明时加了mutable(只能修饰非静态非const)的变量,也可以在const成员函数中改变。

1.4 Observer 模式

Observable 维护了一个指针数组,每个元素指向一个 Observer ,调用 notifyObservers 调用各个 Observer 的 update 方法;

1.8 节中 Observer 对象也是由 shared_ptr 管理的;

1.5 MutexLockGuard

MutexLock 封装了一个互斥锁和一个线程 ID 变量,从而可以判断当前线程是否持有该锁;
MutexLockGuard 对上述进一步封装,构造时调用 lock,析构时调用unlock

2. 线程安全的对象生命期管理

// 内部调用 gettid() ,并缓存了线程 tid;
CurrentThread::tid()		

2.1 对象的构造

要做到线程安全,唯一的要求是在构造期间不要泄漏 this 指针;

不要在构造函数中注册任何回调;
不要在构造函数中把 this 传给跨线程的对象;(构造期间对象没有完成初始化,其他线程访问时可能是半成品,造成难以预料的后果)
即使在构造函数的最后一行也不行;(因为该类可能是基类,之后要去执行子类的构造函数)

2.2 对象的析构

(1)一个对象可被多线程观察到,线程 A 调用析构函数,线程 B 调用该对象其他函数,就可能会导致意想不到的后果;

线程 A 可能获取到锁先执行,线程 B 阻塞在锁处;但析构函数会把锁也释放掉;

(2)只要别的线程都访问不到这个对象时,析构才是安全的;

(3)一个函数如果要锁住相同类型的多个对象,为保证始终按相同顺序加锁(避免潜在死锁,例如线程 A 调用 swap(a,b),线程 B 调用 swap(b,a)),可以先比较 mutex 对象的地址,始终先加锁地址较小的 mutex;

2.3 shared_ptr 的使用

使用 shared_ptr 来管理对象

2.3.1 weak_ptr

可以判断对象是否还存活;如果对象存活,可以提升(lock())为 shared_ptr,否则,返回一个空的 shared_ptr,提升lock()行为是线程安全的

2.3.2 enable_shared_from_this

直接从原始指针构造的各 shared_ptr 不会共享引用计数,这就可能会导致重复释放问题;

enable_shared_from_this 使得从 当前被 pt 管理的对象 t 安全的生成其他 shared_ptr 实例(这些都共享 t 的所有权)

2.3.3 swap 技巧
void write()
{
	shared_ptr<Foo> newPtr(new Foo); // 注意,对象的创建在临界区之外
	{
		MutexLockGuard lock(mutex);
		globalPtr = newPtr; // write to globalPtr 
	}
	// use newPtr since here,读写newPtr 无须加锁
	doit(newPtr);
}

上述代码中原先 globalPtr 指向对象的析构可能会发生在临界区,可以利用 swap 技巧将其推迟到临界区之外;

void write()
{
	shared_ptr<Foo> newPtr(new Foo); // 注意,对象的创建在临界区之外
	shared_ptr<Foo> newPtr1(newPtr);
	{
		MutexLockGuard lock(mutex);
		swap(newPtr1, globalPtr); // write to globalPtr 
	}
	// use newPtr since here,读写newPtr 无须加锁
	doit(newPtr);
}
2.3.4 注意事项

(1)意外延长对象的生命期

boost::bind 会把实参拷贝一份,如果参数是个 shared_ptr,那么对象的生命期就不会短于 boost::function 对象

class Foo
{
	void doit();
};
shared_ptr<Foo> pFoo(new Foo);
boost::function<void()> func = boost::bind(&Foo::doit, pFoo); // long life foo 这里 func 持有 shared_ptr 的一份拷贝

(2)析构动作在创建时捕获;

shard_ptr<T> ptr( new T1 ); 

注意,构造 shard_ptr 时,使用 模板类型推导 保存了 T1 的类型(而非 T 的类型,因为 T 可能是 T1 的基类),从而可以调用其相应析构函数(即使基类的析构函数不是虚函数,详见 STL源码分析:shared_ptr 和 weak_ptr;

shared_ptr<void> 可以持有任何对象,也能保证安全的释放;

(3)如果对象的析构比较耗时,可以使用一个单独的线程专门做析构

可以使用一个 BlockingQueue<shared_ptr<void> >

2.3.5 对象池

只允许出现一个 T 对象,多线程同时访问同一个对象时,其应该被共享;

自然的想法是使用 shared_ptr 管理对象,但引用计数变为 0 时,虽然可以释放对象 T ,但无法减小哈希表的大小;

std::map<string, weak_ptr<T>> mt_;

解决办法是:使用智能指针的定制析构功能,在析构 T 对象时同时清理哈希表;

class StockFactory : boost::noncopyable
{
	// 在get() 中,将pStock.reset(new Stock(key)); 改为:
	// pStock.reset(new Stock(key),
	//				boost::bind(&StockFactory::deleteStock, this, _1)); // ***
private:
	void deleteStock(Stock* stock)
	{
		if (stock) {
			MutexLockGuard lock(mutex_);
			stocks_.erase(stock->key());
		}
		delete stock; // sorry, I lied
	}
.......
};

bind 中使用到了 this 指针这种做法要求 StockFactory 不能先于 Stock 对象析构,否则会 core dump;

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1338804.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

【SpringBoot篇】优惠券秒杀 — 添加优惠劵操作(基本操作 | 一人仅一张券的操作)

文章目录 &#x1f354;发放优惠券&#x1f386;基本操作&#x1f384;数据库表&#x1f6f8;思路&#x1f339;代码实现 &#x1f386;完善后的操作&#x1f6f8;乐观锁&#x1f339;代码实现 &#x1f354;一人仅一张优惠券&#x1f6f8;思路&#x1f339;代码⭐代码分析 &am…

Java学习——设计模式——介绍

文章目录 设计模式介绍UML的类图表示类与类之间关系的表示关联关系聚合关系组合关系依赖关系继承关系实现关系 设计模式介绍 设计模式design patterns&#xff0c;指在软件设计中&#xff0c;被反复使用的一种代码设计经验。使用设计模式的目的是为了可重用代码&#xff0c;提…

简单的喷淋实验(2):(1)根据土壤湿度自动控制喷淋开关;(2)根据光照强度控制风扇以及灯的开关---嵌入式实训

目录 简单的喷淋实验(2)&#xff1a; &#xff08;1&#xff09;根据土壤湿度自动控制喷淋开关&#xff1b; &#xff08;2&#xff09;根据光照强度控制风扇以及灯的开关---嵌入式实训 任务2&#xff1a; 具体过程&#xff1a; 所用的头文件&#xff1a; data_global.h …

BDD - Python Behave Retry 机制

BDD - Python Behave Retry 机制 引言Behave RetryBehave Retry 应用feature 文件创建 step 文件Retry运行 Behave 并生成 rerun 文件重新运行失败的场景 引言 在日常运行测试用例&#xff0c;有时因为环境不稳定造成一些测试用例跑失败了&#xff0c;如果能将这些失败的测试用…

少走弯路:单片机使用点阵字体通过像素化的正确获取

要在单片机内自由显示文字&#xff0c;必须准备相应的字库。之前也发文介绍过&#xff1a; 在esp32(esp8266) 提供软字库显示中文的解决方案_esp32中文字库-CSDN博客 包括已经开源的项目&#xff1a; https://github.com/StarCompute/tftziku 这种字体获取思路是&#xff1a…

test mock-01-什么是 mock? Mockito/EasyMock/PowerMock/JMockit/Spock mock 框架对比

拓展阅读 test 之 jmockit-01-overview jmockit-01-test 之 jmockit 入门使用案例 mockito-01-overview mockito 简介及入门使用 PowerMock Mock Server ChaosBlade-01-测试混沌工程平台整体介绍 jvm-sandbox 入门简介 单元测试中的 mock 单元测试是一种验证代码单元&…

echart实现自定义图例文字颜色

1.效果图 2.html <div class"biao" id"biao1"></div> 2.js 关键&#xff1a; color: [#2db7f5, #ff6600, #921AFF, #c32441, #FF00FF, #FF8EFF, #53FF53, #F9F900, #00FFFF],//关键:自定义图例文字颜色在legend中添加data,将数据处理成如下…

git远程操作,推送【push】,拉取【pull】,忽略特殊文件,配置别名,标签管理

文章目录 前言&#xff1a;新建远程仓库克隆推送【push】拉取【pull】 配置git忽略特殊文件给命令配置别名 标签管理理解标签创建标签操作标签 前言&#xff1a; 大家如果没有看过前几章git的基础操作的话&#xff0c;推荐先看一下&#xff0c;看完再来看这个远程操作&#xf…

查看ios app运行日志

摘要 本文介绍了一款名为克魔助手的iOS应用日志查看工具&#xff0c;该工具可以方便地查看iPhone设备上应用和系统运行时的实时日志和奔溃日志。同时还提供了奔溃日志分析查看模块&#xff0c;可以对苹果奔溃日志进行符号化、格式化和分析&#xff0c;极大地简化了开发者的调试…

单集群400TB,OceanBase稳定支撑快手核心业务场景

一款日均超过千万人访问的短视频 App 快手&#xff0c;面对高并发流量如何及时有效地处理用户请求&#xff1f;通过在后端配置多套 MySQL 集群来支撑高流量访问&#xff0c;以解决大数据量存储和性能问题&#xff0c;这种传统的 MySQL 分库分表方案有何问题&#xff1f;快手对分…

个人游戏启动器 | 游戏数据库 playnite 折腾记录

环境&#xff1a;Windows 11 问题&#xff1a;使用平板串联PC游戏后&#xff0c;需要一个本地的PC启动器 解决办法&#xff1a;使用playnite搭配插件 背景&#xff1a;我是个单机游戏爱好者&#xff0c;因为某些原因&#xff0c;需要串流游玩&#xff0c;需要一个方便手柄操作的…

安全运维是做什么的,主要工作内容是什么

安全运维&#xff0c;简称SecOps&#xff0c;是一种集成安全措施和流程到信息技术运维的实践。它的目的是确保在日常运维活动中&#xff0c;如网络管理、系统维护、软件更新等&#xff0c;均考虑并融入安全策略。安全运维的核心是实现安全和运维团队的密切协作&#xff0c;以快…

鸿蒙系列--组件介绍之其他基础组件(上)

上回介绍了基础组件中最常用的组件常用的基础组件&#xff0c;接下来还有其他基础组件 一、Blank 描述&#xff1a;空白填充组件 功能&#xff1a;在容器主轴方向上&#xff0c;具有自动填充容器空余部分的能力。只有当父组件为Row/Column时生效 子组件&#xff1a;无 Blan…

Echarts使用,Echarts图表自适应窗口大小

Echarts官方文档 1.下载Echarts 项目打打开终端直接通过命令 npm install echarts --save 下载完成后在项目package.json查看。 2.使用Echarts 引入方式有两种全局引入和局部引入 全局引入直接在项目main.js引入放到vue原型上。 import * as echarts from echarts Vue.pr…

《Linux C/C++服务器开发实践》:深入探索网络编程的基础知识与实用技术

&#x1f3a5; 屿小夏 &#xff1a; 个人主页 &#x1f525;个人专栏 &#xff1a; 书籍推荐 &#x1f304; 莫道桑榆晚&#xff0c;为霞尚满天&#xff01; 文章目录 &#x1f4d1;前言一. 构建高性能Linux C/C服务器1.1 优化服务器性能1.2 处理并发和并行性1.3 高效管理内存1…

【新资讯】《网络安全事件报告管理办法(征求意见稿)》正在公开征求意见

近年来网络安全事故频发&#xff0c;造成了不少损失和危害。为了减少网络安全事故的发生&#xff0c;规范网络安全事件的报告&#xff0c;国家互联网信息办公室根据《中华人民共和国网络安全法》等法律法规起草了《网络安全事件报告管理办法&#xff08;征求意见稿&#xff09;…

【Linux基础】5. 磁盘管理

文章目录 【 1. 查看磁盘空间 】1.1 df 查看空间利用大小1.2 du 查看目录所占空间大小 【 2. 打包、压缩 】2.1 tar -cvf 打包2.2 gzip 压缩 【 3. 解压缩、解包 】3.1 gunzip 解压缩3.2 tar -xvf 解包 【 1. 查看磁盘空间 】 1.1 df 查看空间利用大小 作用 查看整个文件系统…

keras 人工智能之VGGNet神经网络的图片识别

VGG16结构图 上期文章我们分享了如何使用VGGNet CNN网络结构搭建一个图片识别网络,以及训练了神经网络模型,利用上期训练好的神经模型,可以进行我们的图片识别 图片识别结果 导入第三方库 from keras.preprocessing.image import img_to_array from keras.models import …

企业级实战项目:基于 pycaret 自动化预测公司是否破产

本文系数据挖掘实战系列文章&#xff0c;我跟大家分享一个数据挖掘实战&#xff0c;与以往的数据实战不同的是&#xff0c;用自动机器学习方法完成模型构建与调优部分工作&#xff0c;深入理解由此带来的便利与效果。 1. Introduction 本文是一篇数据挖掘实战案例&#xff0c;…

美国某金融公司遭遇网络攻击,130 万民众受影响

The Record 网站披露&#xff0c;美国最大的产权保险公司富达国民金融&#xff08;Fidelity National Financial&#xff08;"FNF"&#xff09;&#xff09;子公司向所在州监管机构报告了一起数据泄露事件&#xff0c;并指出有 1316938 人的数据信息被入侵其母公司的…