分布式实时处理系统——C++高性能编程 RAII resource acquisition is initialization

分布式实时处理系统——C++高性能编程

  【前言】基于通信基础,介绍Hurricane实时处理系统的工程实现,主要使用C++语言。

一、IPC、socket、异步I/O epoll

二、C++11

  1、linux内存管理中使用RALL(resource acquisition is initialization)原则,C++通过加入 类的构造函数和析构函数 解决资源管理问题。让编译器自己去调用析构函数释放资源。

  2、类对象的值传递问题会导致多次析构,使用智能指针;

  3、C++怎么实现的线程和锁机制;

  4、多线程问题下的内存屏障(禁止编译器优化)、CPU内存屏障(原子操作);

  5、C++中的内存分配和碎片处理,使用更好的C++内存管理器代替默认的,如google的tcmalloc会在链接时期替代标准libc中的malloc和free;

  6、内存池:在一块内存上建立内存管理机制,使用分配算法来适应多变的零散内存申请需求;

RAII 是 resource acquisition is initialization 的缩写,意为“资源获取即初始化”。

它是 C++ 之父 Bjarne Stroustrup 提出的设计理念,其核心是把资源和对象的生命周期绑定,对象创建获取资源,对象销毁释放资源。

在 RAII 的指导下,C++ 把底层的资源管理问题提升到了对象生命周期管理的更高层次。

  • 那么到底什么是 RALL 机制?

使用 C++ 时,最让人头疼的便是内存管理,但却又正是对内存高度的可操作性给了 C++ 程序猿极大的自由与装逼资本。

当我们 new 出一块内存空间,在使用完之后,如果不使用 delete 来释放这块资源则将导致内存泄露,这在中大型项目中是极具破坏性的。

但是人无完人,我们并不能保证每次都记得释放无法再次获取到且不再使用的内存,下面我给出一个例子,大家看看忘记释放资源而造成内存泄露是多么恐怖!!

#include <iostream>  
#include <memory>  

int main()  
{  
    for (int i = 1; i <= 10000000; i++)  
    {  
        int32_t *ptr = new int32_t[3];  
        ptr[0] = 1;  
        ptr[1] = 2;  
        ptr[2] = 3;  
        //delete ptr;     //假设忘记了释放内存  
    }  
    system("pause");  
    return 0;  
}  

运行程序,打开资源管理器,可以这么简单的一个程序竟然就已经占用了500 多MB的内存,所以大家应该祈祷千万不要犯这么低级的错误!



有没有什么方法能够保证资源的自动释放呢?就像Java一样,但是却又不失C++程序猿的面子

这个时候我们想到对象的析构是自动完成的,那么可不可以利用这个机制呢?

答案很明确,可以。我们需要做的便是将资源托管给某个对象,或者说这个对象是资源的代理,在这个对象析构的时候完成资源的释放。

于是我们可以将上例改成如下形式:

#include <iostream>  
#include <memory>  

  //先创建一个模板类, 负责资源的释放, 当资源生命周期结束, 自动释放资源;
template<typename T>  
class auto_release_ptr  
{  
public:  
    auto_release_ptr(T *t) :_t(t){};  
    ~auto_release_ptr()  
    {  
        delete _t;  
    };  

    T * getPtr()  
    {  
        return _t;  
    }  

private:  
    T *_t;  
};  

int main()  
{  
    for (int i = 1; i <= 10000000; i++)  
    {  
        auto arp = auto_release_ptr<int32_t>(new int32_t[3]);  
        int32_t *ptr = arp.getPtr();  
        ptr[0] = 1;  
        ptr[1] = 2;  
        ptr[2] = 3;  
    }  
    system("pause");  
    return 0;  
}  

然后内存占用变成了这样: 只占用0.5M;

太棒了,只用每次 new 的时候将其传给我们的模板类 auto_release_ptr 就可以防止内存泄露了!让我们来看看这是怎么实现的。

当我们使用 new 出一块内存的时候,我们将其传给了模板类 auto_release_ptr,再通过其实例的 getPtr() 方法得到了内存地址。

auto_release_ptr 有一个数据成员在构造时完成了初始化并指向了 new 出来的空间

而在auto_release_ptr 析构函数中,我们使用 delete 来释放这块内存空间,于是我们 new 出来的资源便有了和 auto_release_ptr 对象一样的生命周期,并且会在其托管的 auto_release_ptr 对象生命周期结束时被释放。

由于 ptr 与 auto_release_ptr 对象的定义是在一块的,所以它们的生命周期自然也是相同的,即便 ptr 被回收时我们也不用再担心其指向的内存空间没有被释放了。

当然,这里只是简单举个例子来说明RALL。

RALL机制便是通过利用对象的自动销毁,使得资源也具有了生命周期,有了自动销毁(自动回收)的功能。

背景

在C++程序运行的过程中免不了要进行资源的分配——尤其是在游戏中!

资源可以有很多种 —— 贴图、音频、Shader到句柄、字符串这些东西都可以被称为资源。

资源的管理是项目中很重要的一轮,做得不好的话轻则内存泄漏、重则内存崩溃。

而RAII则是在C++项目中用于资源管理的一种重要的编程思想。

Class的构建和析构

C++中不可或缺的东西就是class,而每个class不可或缺的就是构造函数和析构函数。

前者用于对象被构造时进行的一系列操作,后者用于对象被析构时所执行的函数。

而值得一提的是,在C++中,如果一个类被声明在栈空间,则在该函数执行完毕从栈空间弹出之后,类会自动调用析构函数。可是如果被显示声明在堆空间(使用new方法或者malloc方法),则需要显式调用析构函数才能进行析构。

RAII

RAII表示的是“资源获取即初始化”(Resource Aquisition Is Initialization),而不是某些人认为的“初始化即资源获取”(Initialization is resource acquisition)。

RAII的技术很简单,利用C++对象生命周期的概念来控制程序的资源。它的技术原理很简单,如果希望对某个重要资源进行跟踪,那么创建一个对象,并将资源的生命周期和对象的生命周期相关联。这样一来C++自带的对象管理设施就可以来管理资源了。

最简单的形式:创建一个对象,让她的构造函数获取一份资源,而析构函数则释放这个资源:

class Resource{...};
class ResourceHandle{
public:
// get resource
explicit ResourceHandle(ResourceHandle *aResource ): r_(aResource){}
// release resource
~ResourceHandle()
{
    delete r_;
}
// get access to resource
Resource *get()
{
    return r_;
}

private:
// make sure it can not be copied by others
ResourceHandle (const ResourceHandle &);
ResourceHandle & operator = (const ResourceHandle &);
Resource *r_;
};

ResourceHandle对象的最好的地方就是:如果它被声明为一个函数的局部变量,或者作为一个参数,或者静态变量,我们都可以保证析构函数得到调用了。这样一来就可以释放对象所引用的资源。

再看看一个反例:

void f() {
    Resource *rh = new Resource;
    //...
    if (blahblah())
        return ;
    //...
    g(); //catch the exceptions
    // Can we make sure that it can be processed here?
    delete rh ;

}
就如同注释所讲,可能一开始的时候上面那段代码是安全的,rh的资源总是可以被释放。

但是如果这段代码经历了一些维护呢?比如说上面的g()函数,有可能会造成函数的提前返回,所以就有可能运行不到最后一句释放资源的代码了,因此这段代码是危险的。

那么该如何使用RAII编程思想对这段代码进行改进呢?代码如下:

void f() {
ResourceHandle rh (new Resource );
// ...
if (blahblah())
return ;
// catch an exception?
g();

//finally the resource would be released by c++ itself.

}

这样一来RAII就使得代码就更加健壮了,因为只要是函数返回了,无论是通过何种途径,那么在返回的时候析构函数就会自动释放资源。

使用RAII只有一种情况无法保证析构函数得到调用,就是当ResourceHandle被动态分配到堆空间上了,这样一来就只能显示得调用delete ResourceHandle对象才能保证资源释放了,比如下面的代码:

ResourceHandle *rhp = new ResourceHandle(new Resource);    

那么此时的问题在于,因为动态分配的东西需要显示调用delete才能释放,所以上面的做法通常是危险的做法。安全的做法是将RAII的handle分配到栈空间去,此时就能够保证内存的释放。

  • 2
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 3
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值