美文网首页
优化:dll资源问题

优化:dll资源问题

作者: WilsonLoo | 来源:发表于2020-10-11 12:37 被阅读0次

背景

在GameServer使用 exit进行优雅关服时,还是coredump,资源回收异常,问题节点在 NFDataProcessModule。


前述(以windows环境为例)

1、在win32环境下,一个【进程】代表一个正在运行的应用程序,或者说代表一个应用程序的实例,而一个【线程】代表进程里代码的一条执行线路。【进程】本身是惰性二不执行任何代码的,每个进程至少有一个【主线程】,由主线程在进程的环境里执行代码(当然,也可以有多个线程)。所以从这个角度来看,在win32下【进程】与【模块】没有区别。

2、在win32环境下,模块分为两种:【进程内模块】和【进程外模块】。【进程内模块】共享进程的内存空间,比如在进程内加载的诸多dll,每个dll可被看作一个独立的模块;【进程外模块】与进程一样,独立运行,可包含自己的诸多dll。

3、一个模块代表的是一个运行中的exe文件或者dll文件,用来代表这个文件中的所有代码和资源(尤其是堆),所以在磁盘上的文件不是模块,载入内存后运行时才叫模块。同样的,一个应用程序调用其他dll中的api时,这些dll文件被载入内存,就产生了不同的【进程内模块】。


逐项技术分析

(一)

使用Dll编程中, 有一种情况是模块Dll-A 有一个消费者Class-Customer 是要使用模块Dll-B 生产(即调用CreateRes进行new 创建)的资源Class-Res:

在class-customer的对象析构时,需要释放类成员mRes的资源,这是不能直接在Dll-A内进行delete mRes; 因为mRes资源并不在模块Dll-A的堆内创建,只能由被创建(进行new)的模块Dll-B进行回收。

总结来说,保持资源创建与释放要保持一致性,申请资源调用 Dll-B::CreateRes(), 释放资源调用 Dll-B::ReleaseRes()。


(二)

考虑到用户未必能够合理地调用ReleaseRes(),进而造成资源泄漏或重复释放,可采用智能指针的方式,即Dll-B不再返回Res的原始指针,而是std::shared_ptr<Res>对象:

使用这种方式是不需要进行“显示地”ReleaseRes()。
在 Dll-B::CreateRes()里创建栈变量时std::shared_ptr<Res>,会通过new内建一个【引用计数器】ReferenceCounter,其再包裹实际的资源new Res;在std::shared_ptr<Res>作为函数值参数传递时,ReferenceCounter不会被重复创建,而是指针被传递;随着栈变量std::shared_ptr<Res>作为函数参数的传递,引用计数随着增加与减少引用数,这些过成功唯一的ReferenceCount对象不会被释放,唯一的Res也不会被释放;

(就当前情景而言)最后只在Dll-A::Customer 有一个作为类成员的std::shared_ptr<Res> mRes;ReferenceCounter只有一个对象并且引用数为1,唯一的Res对象也只有一个,被包裹在ReferenceCount,到此资源没有泄露,也没有多余。

后来Dll-A 移除了customer对象,会调用customer的析构函数,自动释放成员 std::shared_ptr<Res> mRes,mRes在被释放时时,调用ReferenceCounter::DecreaseRefCount()以减少引用数,这个是很关键理解,因为ReferenceCounter::DecreaseRefCount()的实现是在Dll-B模块内!!!所以ReferenceCounter 的引用数为0时会在Dll-B模块内做两件事:
1、在Dll-B模块内,调用对实际资源 class-Res的释放:delete res;
2、在Dll-B模块内,调用ReferenceCount的释放:delete self;

这样,ReferenceCounter和class-Res的 创建与析构都发生在 Dll-B内,资源回收正确。


(三)

以上讨论都是基于一个前提,class-customer和 class-res 的释放与析构都是在 Dll-A和Dll-B 模块卸载前进行。在【背景】里提到的“近期在GameServer使用exit进行优雅关服时,还是coredump,资源回收异常,问题节点在 NFDataProcessModule”,则没有确保这个“前提”。

在GameServer 进行关服时,Dll-A还未“显示地”释放class-custmoer对象前,Dll-B就被卸载了,导致后面释放析构class-custmoer,会调用customer的析构函数,自动释放成员std::shared_ptr<Res> mRes,mRes在被释放时时,调用ReferenceCounter::DecreaseRefCount()以减少引用数,这个是很关键理解,因为ReferenceCounter::DecreaseRefCount()的实现是在Dll-B模块内!!!所以ReferenceCounter 的引用数为0时会在Dll-B模块内做两件事:

1、在Dll-B模块内,调用对实际资源 class-Res的释放:delete res:但是,Dll-B已经卸载了,此处实际就是对野指针进行了delete,进而coredump;

2、(无法继续)在Dll-B模块内,调用ReferenceCount的释放:delete self;

总结:虽然std::shared_ptr 的引入有利确保dll之间的资源的管理,但是如果dll的卸载顺序没有维护好,依然会产生异常。


(四)

采用模块化资源管理的思想进行dll资源的管理:

思想是,将一个应用或功能设计成module,module至少包含两个操作步骤:资源释放 与 对象析构:【资源释放】,任何module在被析构前,都要先调用ReleaseRes()进行资源的释放,一般例如写入数据、关闭网络连接、关闭数据连接、做好logger等等功能逻辑相关的操作;【对象析构】,因为已经上一步已经做好了数据的管理、资源的释放,这里不会再进行功能逻辑的操作,一般只进行delete self操作。

这种做法是,第一步在所有dll现在之前,都必须先调用 IModule::ReleaseRes()进行资源的释放与回收;第二步安心地进行dll卸载。


(五)

其实GameServer 进行已经使用模块化的思想进行设计,其有两个dll模块进行交互:

模块GameLogicPlugin.dll 往 模块 DataProcessPlugin.dll 注册了一个资源Res,但是在DataProcessPlugin.dll::DataProcessModule::OnShutdown()没有进行资源的释放,最后GameLogicPlugin.dll 先卸载,接着DataProcessPlugin.dll 卸载进行析构时mResList 不为空,尝试移除mResList时,由于GameLogicPlugin.dll已经卸载,不能继续释放res资源,导致出现问题。如果程序员能够在各个module的OnShutdown() 正确地进行资源释放,则不会出现这些问题。


(六)

为了避免由于某些程序员由于疏忽而忘记在 module::OnShutdown() 进行资源释放,在此引入【模板模式设计模式】和借鉴【boost::signal2::connection】在观察者模式的实现机制。

该设计模式的核心思想是,在接口只提供对外的 Start()/Shutdown() 接口,并且由这两个接口定制好具体的步骤,置于这些步骤的具体实现,则由子类实现(多态)。

用boost::signal2 实现的观察者模式中,采用了connection对象的引入,其作用是: 1、“显示地”解除观察者与被观察者之间的 “观察关系”; 2、只要任何一方被析构了,都会自动进行“观察关系”的解绑:

置于connection的工作原理和详细设计,此处不再详述(可自行百度boost::signal观察者模式实现、或精灵项目的 Observable.hpp的实现),另外为了呼应前述的“要放在对象/模块释放前进行资源回收”,这一小节采用connection的【“显示地”解除观察者与被观察者之间的“观察关系”机制】:

在这种设计中,模块进行资源注册时,直接调用AddRes(),LogicModuleBase会自动建立连接connection并绑定;在dll进行卸载前,由模块基类提供的 shutdown自动进行connection的断开、同时因为connection维护了连接双方和资源的信息,此时connection的断开会自动进行资源的回收(例如std::vector<Res>的清空)。可以看到,随着新模块的增多,负责新模块的程序员只需要进行资源的注册AddRes(),就行了,module框架会自动进行shutdown 和 资源的回收。


以上。

相关文章

  • 优化:dll资源问题

    背景 在GameServer使用 exit进行优雅关服时,还是coredump,资源回收异常,问题节点在 NFDa...

  • 项目中内存泄漏、不正规编程等各种问题

    一、dll间调用顺序不明朗,导致资源释放错乱 修正【正常关服】时资源回收异常的问题。项目大量使用dll进行组件化设...

  • [.net]如何加载位于资源中的dll

    原文 适用于将dll集成为资源的项目(添加dll文件,设置编译动作为集成资源) AppDomain.Current...

  • 前端性能优化-开篇

    前端性能优化问题是每个前端需要掌握的技术。这篇文章从渲染优化、代码优化、资源优化、构建优化、传输加载优化、更多流行...

  • Unity包体优化建议

    这是一个关于Unity游戏包体优化方案的全面介绍 打包规则 所有非editor下的托管代码会编译为dll,dll会...

  • Python3练习:调用DLL文件

    一、Python调用dll文件 二、遇到的问题 (一)问题一 (1)遇到问题 (2)问题分析 没有找到该DLL文件...

  • Webpack dll优化实战

    DLL是什么,用它来干啥?   DLL(Dynamic Link Libray)原来特指windows系统中实现共...

  • webpack构建优化—dll

    随着项目越来越大,项目里的依赖包也越来越多,所以打包和项目启动速度会变慢。这里介绍DllPlugin和DllRef...

  • 06:项目管理进度21

    资源优化(P211)资源优化技术----根据资源供需情况,来调整进度模型的技术。包括“资源平衡”和“资源平滑”资源...

  • 一张图看明白云计算架构核心竞争力

    低TCO 计算资源的成本优化和节省 存储资源的成本优化与节省 网络资源的成本优化与节省 维护成本的优化与节省 节能...

网友评论

      本文标题:优化:dll资源问题

      本文链接:https://www.haomeiwen.com/subject/dxndpktx.html