其他分享
首页 > 其他分享> > Smart Pointers(智能指针)与Epoch-Based Reclamation (EBR)详细介绍和对比

Smart Pointers(智能指针)与Epoch-Based Reclamation (EBR)详细介绍和对比

作者:互联网

Smart Pointers

它基于三个概念,包括堆栈分配的指向堆分配内存的指针,扮演每个对象的垃圾收集角色和 RAII。
有 3 个 API:unique_ptr、shared_ptr 和 weak_ptr。
1)unique_ptr 只允许底层指针的一个所有者,但不支持复制,但支持移动语义。 然后,当所有者指针超出范围时,它的内存被回收。
因此,它不需要显式调用“删除”。 更重要的是,unique_ptr 可以自我记录并捕获编程错误。
尽管如此,它通常需要复制指针。

  1. unique_ptr
    只允许底层指针的一个所有者。
    当所有者指针超出范围时回收内存
void heap_alloc_foo(){
    std::unique_ptr<MyClass> valuePtr(new MyClass(15));
/*
*Does not support copying, but support move semantics
*   Lightweight, but limits use of pointers
*/
    std::unique_ptr<MyClass> p2 = p1; //this will raise compile-time error
    std::unique_ptr<MyClass> p3(std::move(p1)); //moves to p2, invalidates p1
}
/*
* As follows:
*/
#include <iostream>
#include <memory>

using namespace std;

class Test {
public:
    Test(int a = 0) : m_a(a) {}
    ~Test() {
        cout << "Calling destructor" << endl;
    }

public:
    int m_a;
};

void heap_alloc_foo(){
	std::unique_ptr<MyClass> valuePtr(new MyClass(15));
/*
*Does not support copying, but support move semantics
*   Lightweight, but limits use of pointers
*/
	std::unique_ptr<MyClass> p2 = p1; //this will raise compile-time error
	std::unique_ptr<MyClass> p3(std::move(p1)); //moves to p2, invalidates p1
}

void Fun(unique_ptr<Test> p1)
{
    cout << p1->m_a << endl;
    cout << "Fun() end" << endl;
}

int main()
{
    heap_alloc_foo();
auto_ptr<Test> p(new Test(5));
    Fun(p);
    cout << p->m_a << endl;

    return 0;
}

结果

在这里插入图片描述
Unique_ptr 是一个很好的 RAII 编程实践,它不需要显式调用“删除”。 它可以自我记录并捕获编程错误。
但通常我们需要复制指针
2.shared_ptr:
Heap_ alloc_foo:

void heap_alloc_foo(){
    std::unique_ptr<MyClass> valuePtr(new MyClass(15));
/*
*Allows more than one owner of the underlying pointer.
*   Memory reclaimed when all owner pointers goes out of scope
*   Uses reference counting
*   Support copying
*/
    std::unique_ptr<MyClass> p2 = p1; //this increases reference count
    std::unique_ptr<MyClass> p3(std::move(p1)); //moves to p2, invalidates p1, no effect on reference count
}

creation :

int main()
{
// share_ptr General creation process of PTR
heap_alloc_foo();
    shared_ptr<int> sptr1( new int );
    //Using make_ Shared to speed up the creation process
    // shared_ PTR automatically allocates memory and guarantees reference count
    //And make_ Shared is initialized in this way
    shared_ptr<int> sptr2 = make_shared<int>( 100 );
    //You can use_ Count() to view the reference count
    cout << "sptr2 referenced count: " << sptr2.use_count() << endl;
    shared_ptr<int> sptr3 = sptr2;
    cout << "sptr2 referenced count: " << sptr2.use_count() << endl;
    cout << "*sptr2 = " << *sptr2 << endl;
    return 0;
}

destruction:

class Test {
public:
    Test(int a = 0) : m_a(a) {}
    ~Test() {
        cout << "Calling destructor" << endl;
    }

public:
    int m_a;
};

int main()
{
    //If it is in the following form, only delete will be called, but not

    // delete[ ]; The destructor is called only once
    shared_ptr<Test> sptr1(new Test[5]);

   //Take the following form, lambda expression, to display the call

    //Delete [] to delete all objects.
    shared_ptr<Test> sptr2(new Test[5],
                           [](Test* p) {delete[] p;});
    return 0;
}

Issue:

int main() 
{
    int *p = new int;
    shared_ptr<int> sptr1(p);
    shared_ptr<int> sptr2(p);
    return 0;
}

在这里插入图片描述
在这里插入图片描述
引用计数表如下:
在这里插入图片描述
3.weak_ PTR :
weak_ PTR 一般是通过 shared_ PTR。 当使用shared_PTR初始化weak_PTR时,break_PTR指向同一个地方,但不改变对象的引用计数。

#include <iostream>
#include <memory>

using namespace std;

class B;
class A
{
public:
    A() : m_a(5) {} ;
    ~A() {
        cout << "A is destroyed" << endl;
    }
    void PrintSpB() ;
    weak_ptr<B> m_sptrB;
    int m_a;
};

class B {
public:
    B() : m_b(10) {} ;
    ~B() {
        cout << "B is destroyed" << endl;
    }
    weak_ptr<A> m_sptrA;
    int m_b;
};

void A::PrintSpB()
{
    if( !m_sptrB.expired() )
        cout << m_sptrB.lock()->m_b << endl;
}

int main()
{
    shared_ptr<B> sptrB(new B);
    shared_ptr<A> sptrA(new A);
    sptrB->m_sptrA = sptrA;
    sptrA->m_sptrB = sptrB;
    sptrA->PrintSpB();

    return 0;
}

1)它有一个非常本质的缺点:当Auto_PTR被分配给另一个auto_PTR对象时,它会传递自己的所有权。
2) 与上一个相比,shared_ptr 允许多个底层指针拥有者,并且支持复制。 同时,当所有所有者指针超出范围时回收内存。此外,它还使用引用计数。
但是,它存在一系列问题。
例如,性能开销、shared_ptr 实例之间可能的循环引用以及不能适应非阻塞实现计数是阻塞的定义。
3)weak_ptr 提供对一个或多个shared_ptr 实例拥有的对象的访问,但不参与引用计数。 它不仅使用weak_ptr 可以缓解之前的问题,而且weak_ptr 通常与智能指针的主要目标背道而驰。

Epoch-Based Reclamation (EBR)[An example of Domain-Specific Reclamation]

Main idea in a more formal way:
• 进程在没有指向数据结构中任何记录的指针时处于静止状态。
• 宽限期:每个进程都有一个处于静止状态的时间点。 (from ε −2/ε −1 switch to ε − 1/ε switch)
 • 安全内存回收
  • unlink(x) – grace period – reclaim(x)

use std::mem::ManuallyDrop;use std::ptr;use std::sync::atomic::Ordering::{Acquire, Relaxed, Release};
use crossbeam_epoch::{self as epoch, Atomic, Owned};
#[derive(Debug)] pub struct TreiberStack <T>{head : Atomic <Node <T>>, }
#[derive(Debug)] struct Node <T>{data : ManuallyDrop <T>, // Tells the compiler that this variable does not need to be automatically dropped   
 next: Atomic<Node<T>>,}
impl<T> TreiberStack<T> {
    pub fn new ()->TreiberStack<T>
    {
        TreiberStack
        {
        head:
            Atomic::null(),
        }
    }
    pub fn push(&self, t
                : T)
    {
        let mut n = Owned::new (Node{
            data : ManuallyDrop::new (t),
            next : Atomic::null(),
        });
        let guard = epoch::pin(); // Mark the current thread as active
        loop
        {
            let head = self.head.load(Relaxed, &guard);
            n.next.store(head, Relaxed);
            match self.head.compare_and_set(head, n, Release, &guard)
            { // CAS                
            Ok(_) => break,                
            Err(e) => n = e.new,           
                 }        
            }   
         }
                pub fn pop(&self)->Option<T>
                {
                    let guard = epoch::pin(); // Mark the current thread as active        
                    loop {            
                        let head = self.head.load(Acquire, &guard);
                    match unsafe{head.as_ref()}
                    {
                        Some(h) = >
                        {
                            let next = h.next.load(Relaxed, &guard);
                            if self
                                .head.compare_and_set(head, next, Relaxed, &guard) // CAS.is_ok()                    {                        //unsafe {                            guard.defer_destroy(head); // add garbage to list                            
return Some(ManuallyDrop::into_inner(ptr::read(&(*h).data))); // return data of node                        }                    }                }                           }        }    }
                                    pub fn is_empty(&self)
                                        ->bool
                                {
                                    let guard = epoch::pin();
                                    self.head.load(Acquire, &guard).is_null()
                                }
                        }
impl<T> Drop for TreiberStack<T>
{
    fn drop(&mut self)
    {
        while
            self.pop().is_some() {}
    }
}

每个线程都有一个本地的limbo 列表,后台线程通过释放内存和退出到对象池来回收内存。
与智能指针相比,它具有低开销和最少的代码修改。
不幸的是,它仅适用于定义良好的数据结构,也就是说,如果指针可以从一个操作传递到另一个操作(假设操作之间处于静止状态),则 EBR 不起作用。 但是,陈旧的操作可能会停止回收。 这增加了很多限制,例如需要仔细设置内存栅栏。

标签:std,head,Based,Pointers,Reclamation,new,shared,unique,ptr
来源: https://blog.csdn.net/weixin_42455006/article/details/121778688