公务员年度考核个人总结,如何避免内存泄漏,一个就够了-安博电竞app-安博电竞-安博电竞app

西甲联赛 309℃ 0

前语

近年来,评论 C++ 的人越来越少了,一format方面是因为像 Python,Go 等优异的言语的盛行,另一方面,咱们也越来越理解一个道理,并不是一切的场景都有必要运用 C++ 进行开发。Python 能够敷衍大部分对功用要求不高的场景,Go 能够敷衍大部分对并发要求较高的场景,而因为 C++ 的杂乱性,只要在对功用极端严苛的场景下,才会考虑运用。

那么究竟多严苛算是严苛呢?Go 自带内存办理,也便是 GC 功用,经过多年的优化,在 Go 中每次 GC 或许会引进 500us 的 STW 推迟。



也便是说,假如你的运用场景能够忍受不定时的 500us 的推迟,那么用 Go 都是没有问题的。假如你无法忍受 500us 的推迟,那么带 GC 功用的言语就根本无法运用了,只能挑选自己办理内存的言语,例如 C++。那么由手动办理内存而带来的编程杂乱度也就随之而来了。

作为 C++ 程序员,内存走漏始终是悬在头上的一颗炸弹。在曩昔几年的 C++ 开发过程中,因为咱们选用了一些技能,咱们的程序发作内存泄公务员年度查核个人总结,怎样防止内存走漏,一个就够了-安博电竞app-安博电竞-安博电竞app露的状况寥寥无几。今日就在这儿向咱们做一个简略的介绍。

内yoyo存是怎样走漏的

在 C++ 程序中,首要涉及到的内存便是『栈』和『堆』(其他部分不在本文中介绍了)。



一般来说,一个线微贷网程的栈内存是有爱丽舍限的,一般来说是 8M 左右(取决于运转的环境)。栈上的内存一般是由编译器来主动办理的。当在栈上分配一个新的变量时,或进入一个函数时,栈的指针会下移,适当于在栈上分配了一块内存。咱们把一个变量分配在栈上,也便是运用了栈上的内存空间。雷洁琼简历当这个变量的生命周期完毕时,栈的指针会上移,相同于收回了内存。

因为栈上的内存的分配和收回都是由编译器操控的,所以在栈上是不会发作内存走漏的,只会发作栈溢出(Stack Overflow),也便是分配的空间超过了规矩的栈巨细。

而堆上的内存是由程序直接操控的,程序能够经过 malloc/free 或 new/delete 来分配和收回内存,假如程序中经过 malloc/new 分配了一块内存,但忘掉运用 free/delete 来收回内存,就发作了内存走漏。

经历 #1:尽量防止在堆上分配内存

已然只要堆上会发作内存走漏,那榜首准则肯定是防止在堆上面进行内存分配,尽或许的运用栈上的内存,由编译器进行分配和收回,这样当然就不会有内存走漏了。

但是,只在栈上分配内存,在有 IO 的状况下是存在必定局限性的。

举个比如,为了完结一个恳求,咱们一般会为这个恳求结构一个 Context 目标,用于描绘和这个恳求有关的一些上下文。例如下面一段代码:

void Foo(Reuqest* req) {
RequestContext ctx(req);
HandleRequest(&ctx);
}

假如 HandleRequest 是一个同步函数,当这个函数回来时,恳求就能够被处理完结,那么明显 ctx 是能够被分配在栈上的。

但假如 HandleRequest 是一个异步函数,例如:

void HandleRequest(RequestContext* 公务员年度查核个人总结,怎样防止内存走漏,一个就够了-安博电竞app-安博电竞-安博电竞appctx, Callback cb);

那么明显,ctx 是不能被分配在栈上的,因为假如 ctx 被分配在栈上,那么当 Foo 函数推出后,ctx 目标的生命周期也就完毕了。而 FooCB 中明显会运用到 ctx 目标。

void HandleRequest(RequestContext* ctx, Callback cb); 
void Foo(Reuqest* req) {
auto ctx = new RequestContext(req);
HandleRequest(ctx, FooCB);
}
void FooCB(RequestContext* ctx) {
FinishRequest(ctx);
delete ctx;
}

在这种状况下,假如忘掉在 FooCB 中调用 delete ctx,则就会触发内存走漏。虽然咱们能够凭借一些静态检查东西对代码进行检查,但往往异步程序的逻辑是极端杂乱的,一个恳求的生命周期中,也需求进行许多的内存分配操作,静态检查东西往往无法发现一切的内存走漏状况。

那么怎样才干防止这种状况的发作呢?引进智能指针明显是一种可行的办法,但引进 shared_ptr 往往引进了额定的功用开支,并不非常抱负。

在 SmartX,咱们一般选用两种办法来应对这种状况。

经历 #2:运用 Arena

Arena 是一种一致化办理内存生命周期的办法。一切需求在堆上分配的内存,不经过 malloc/new,而是经过 Arena 的 CreateObject 接口。一同,不需求手动的履行 free/delete,而是在 Arena 被毁掉的时分,一致开释一切经过 Arena 目标恳求的内存。所以,只需求保证 Arena 目标必定被毁掉就能够了,而不必再关怀其他目标是否有漏掉的 free/delete。这样显裴涩琪然降低了内存办理的杂乱度。

此外,咱们还能够将 Arena 的生命周期与 Request 的生命周期绑定,一个 Request 生命周期内的一切内存分配都经过 Arena 完结。这样的优点是,咱们能够在结构 Arena 的时分,大约预估出处理完结这个 Request 会耗费多少内存,并提早将会运用到的内存一次性的恳求完结,然后减少了在处理一个恳求的过程中,分配和收回内存的次数,然后优化了功用。

咱们最早看到 Arenregulara 的思维,是在 LevelDB 的代码中。这段代码适当简略,主张咱们直接阅览。

经历 #3:运用 Coroutine

Coroutine 信任咱们并不生疏,那 Coroutine 的实质是什么?我以为 Coroutine 的实质,是使得一个线程中能够存在多个上下文,大龙虾并能够由用户操控在多个上下文之间进行切换。而在上下文中,一个重要的组成部分,便是栈指针。运用 Coroutine,意味着咱们在一个线程中,能够发明(或模仿)多个栈。

有了多个栈,意味着当咱们要做一个异步处理时,不需求开释当时栈上的内存,而只需求切换到另一个栈上,就能够持续做其他的工作了,当异步处理完结时,公务员年度查核个人总结,怎样防止内存走漏,一个就够了-安博电竞app-安博电竞-安博电竞app能够再切换回到这个栈上,将这个恳求处理完结。

仍是以方才的代码为示例:

void Foo(Reuqest* req) { 
RequestContext ctx(req);
HandleRequest(&ctx);
}
void HandleRequest(RequestCtx* ctx) {
SubmitAsync(ctx);
Coroutine::Self()->Yield();
CompleteRequest(ctx);
}

这儿的精华在于,虽然 Coroutine::Self()->Yield() 被调用时,程序可公务员年度查核个人总结,怎样防止内存走漏,一个就够了-安博电竞app-安博电竞-安博电竞app以跳出 HandleRequest 函数去履行其他代码逻辑,但当时的栈却被保存了下来,所以 ctx 目标是安全的,并没有被开释。

这样一来,咱们就能够彻底扔掉在堆上恳求内存,仅仅用栈上的内存,就能够完结恳求的处理,彻底不必考虑内存走漏的问题。但是这种假定过于抱负,因为在栈上恳求内存存在必定的约束,例如栈巨细的约束,以及需求在编译是知道分配内存的巨细,所以在实践场景中,咱们一般会结合运用 Arena 和 Coroutine 两种技能一同运用。

有人或许会说到,想要多个栈用多个线程不就能够了?但是用多线程完成多个栈的问题公务员年度查核个人总结,怎样防止内存走漏,一个就够了-安博电竞app-安博电竞-安博电竞app在于,线程的创建和毁掉的开支极大,且线程间切块,也便是在栈之间进行切换的代销需求经过操作系统,这个开支也是极大的。所以想用线程模仿多个栈的主意在实践场景中是走不通的。

关于 Coroutine 有许多开源的完成方法,咱们能够在 github 上找到许多,C++20 规范也会包括 Coroutine 的支撑。在 SmartX 内部,咱们很早就完成了 Coroutine,并对所人皮灯笼有异步 IO 操作进行了封装,示例可参阅咱们之前的一篇文章 smartx:根据 Coroutine 的异步 RPC 结构示例(C++)

这儿需求着重一下,Coroutine 的确会带来必定的功用开支,通公务员年度查核个人总结,怎样防止内存走漏,一个就够了-安博电竞app-安博电竞-安博电竞app常 Coroutine 切换的开支在 20ns 以内,但是咱们仍然在对功用要求很严苛的场景运用 Coroutine,一方面是因为 20ns 的功用开支是相对很小的,另一方面是因为 Coroutinreare 极大的降低了异步编程的杂乱度,降低了内存走漏的或许性,使得编写异步程序像编写同步程序相同公务员年度查核个人总结,怎样防止内存走漏,一个就够了-安博电竞app-安博电竞-安博电竞app简略,降低了程序员心智的开支。



经阿信验 #4:善用 RAII

虽然在有些场景运用了 Coroutine,但仍是或许绝代艳后会有在堆上恳求内存的需求,而此刻有或许 Arena 也并不适用。在这种状况下,善用 RAII(Resource Acquisition Is Initialization)思维会协助咱们处理许多问题。

简略来说,RAII 能够协助咱们将办理堆上的内存,简化为办理栈上的内存,然后到达运用编译器主动处理内存收回问题的作用。此外,RAII蒲草根 能够简化的还不仅仅是内存办理,还能够简化对资源的管左归丸理,例如 f菊苣d,锁,引证计数等等。

当咱们需求在堆上分配内存时,咱们能够一同在栈上面分配一个目标,让栈上面的目标对堆上面的目标进行封装,用时经过在栈目标的析构函数中开释堆内存的方法,将栈目标的生命周期和堆内存进行绑定。

unique_ptr 便是一种很典型的比如。但是 unique_ptr 办理的目标类型只能是指针,关于其他的资源,例如 fd,咱们能够经过将 fd 封装成别的一个 FileHandle 目标市侩的方法办理,也能够选用一些更通用的方法。例如,在咱们内部的 C++ 根底库中完成了 Defer 类,主意类似于 Go 中 defer。

void Foo() {
int fd = open();
Defer d = [=]() { close(fd); }
// do something with fd
}

经历 #5:便于 Debug

在特定的状况下,咱们不免仍是要手动办理堆上的内存。但是当咱们面对一个正在发作内存走漏线上程序时,咱们应该怎样处理呢?

当然不是简略的『重启大法好』,究竟重启后仍是或许会发作走漏,并且最名贵的现场也被破坏了。最佳的方法,仍是运用现场进行 Debug,这就要求程序具有便于 Debug 的才能。

这儿不得不说到一个经典而强壮的东西 gperftools。gperftools 是 google 开源的一个东西集,包括了 tcmalloc白萝卜,heap profiler,heap checker,cpu profiler 等等。gperftools 的作者之一,便是大名鼎鼎的 Sanjay Ghemawat,没错,便是与 Jeff Dean 齐名,并和他一同写 MapReduce 的那个 Sanjay。



gperftools 的一些经典用法,咱们就不在这儿进行介绍了,咱们能够自行检查文档。而运用 gperftools 能够在不重启程序的状况下,进行内存走漏检查,这个恐怕是很少有人了解。

实践上咱们 Release 版别的 C++ 程序可履行文件在编译时全部都链接了 gperftools。在 gperftools 的 heap profiler 中,供给了 HeapProfilerStart 和 HeapProfilerStop 的接口,使得咱们能够在运转时发动和中止 heap profiler。一同,咱们每个程序都暴露了 RPC 接口,用于接纳操控指令和调试指令。在调试指令中,咱们就增加了调用 HeapProfilerStart 和 HeapProfilerStop 的指令。因为链接了 tcmalloc,所以 tcmalloc 能够获取一切内存分配和收回的信息。当 heap profiler 发动后,就会定时的将程序内存分配和收回的行为 dump 到一个临时文件中。

当程序运转一段时间后,你将得到一组 heap profile 文件

profile.0001.heap

profile.0002.heap

...

profile.0100.heap

每个 profile 文件中都包括了一段时间内,程序中内存分配和收回的记载。假如想要找到内存走漏的头绪,能够经过运用

pprof --base=profile.0001.heap /usr/bin/xxx profile.0100.heap --text

来进行检查,也能够生成 pdf 文件,维多利亚港会更直观一些。



这样一来,咱们就能够很便利的对线上程序的内存走漏进行 Debug 了。

写在最终

C++ 可谓是最杂乱、最灵敏的言语,也最简单给咱们带来困扰。假如想要用好 C++,团队有必要坚持比较老练的心态,团队成员有必要乐意依照必定的规矩来运用 C++,而不性感热舞激怒高层是固执的随意发挥。这样咱们才干把更多精力放在事务自身,而不是编程言语的特性上。

假如你在C/C++以及后台服务开发方面有什么困惑的话,咱们建了一个QQ群:762073882,谢绝广告。

  会上,与会人员观看了中国农业开展

槟榔是什么,邓苏雪出席市银企对接交流会,强调促进金融业和实体经济共同发展-安博电竞app-安博电竞-安博电竞app