分佈式實時處理系統——C++高性能編程 RAII resource acquisition is initialization

分佈式實時處理系統——C++高性能編程

  【前言】基於通信基礎,介紹Hurricane實時處理系統的工程實現,主要使用C++語言。

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

二、C++11

  1、linux內存管理中使用RALL原則,C++通過加入 類的構造函數和析構函數 解決資源管理問題。讓編譯器自己去調用析構函數釋放資源。

  2、類對象的值傳遞問題會導致多次析構,使用智能指針;

  3、C++怎麼實現的線程和鎖機制;

  4、多線程問題下的內存屏障(禁止編譯器優化)、CPU內存屏障(原子操作);

  5、C++中的內存分配和碎片處理,使用更好的C++內存管理器代替默認的,如google的tcmalloc會在鏈接時期替代標準libc中的malloc和free;

  6、內存池:在一塊內存上建立內存管理機制,使用分配算法來適應多變的零散內存申請需求;

 

 

  1. RAII 是 resource acquisition is initialization 的縮寫,意爲“資源獲取即初始化”。它是 C++ 之父 Bjarne Stroustrup 提出的設計理念,其核心是把資源和對象的生命週期綁定,對象創建獲取資源,對象銷燬釋放資源。在 RAII 的指導下,C++ 把底層的資源管理問題提升到了對象生命週期管理的更高層次。

  2. 那麼到底什麼是 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的內存,所以大家應該祈禱千萬不要犯這麼低級的錯誤!

3. 沒有什麼方法能夠保證資源的自動釋放呢?就像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;

4. 太棒了,只用每次 new 的時候將其傳給我們的模板類 auto_release_ptr 就可以防止內存泄露了!讓我們來看看這是怎麼實現的。

當我們使用 new 出一塊內存的時候,我們將其傳給了模板類 auto_release_ptr,再通過其實例的 getPtr() 方法得到了內存地址。auto_release_ptr 有一個數據成員在構造時完成了初始化並指向了 new 出來的空間,而在其析構函數中,我們使用 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);    
  • 1

那麼此時的問題在於,因爲動態分配的東西需要顯示調用delete才能釋放,所以上面的做法通常是危險的做法。安全的做法是將RAII的handle分配到棧空間去,此時就能夠保證內存的釋放。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章