C++ 危险指针如何在不使用锁的情况下安全地回收并发数据结构中的内存?

C++ 危险指针如何在不使用锁的情况下安全地回收并发数据结构中的内存?,c++,multithreading,C++,Multithreading,我读过几次Maged Michael描述危险指针的文章,我不太明白这里的诀窍 示例是一个映射,它在写入时进行复制—创建当前映射的新映射副本,插入新的键值对,并用新映射交换指向当前映射的指针 因此,每个线程都保留一个线程本地映射指针列表,这些映射指针是在执行写/交换后要丢弃(退役)的。在更新结束时,它对失效指针列表进行扫描,找到所有未通过“获取”方法分配给读取线程的指针,并将其删除 我看不出Acquire在做什么。初始地图如何在危险指针列表中结束?可能有一些代码丢失了。即使它被引导到危险指针列表中

我读过几次Maged Michael描述危险指针的文章,我不太明白这里的诀窍

示例是一个映射,它在写入时进行复制—创建当前映射的新映射副本,插入新的键值对,并用新映射交换指向当前映射的指针

因此,每个线程都保留一个线程本地映射指针列表,这些映射指针是在执行写/交换后要丢弃(退役)的。在更新结束时,它对失效指针列表进行扫描,找到所有未通过“获取”方法分配给读取线程的指针,并将其删除

我看不出Acquire在做什么。初始地图如何在危险指针列表中结束?可能有一些代码丢失了。即使它被引导到危险指针列表中,我们如何确保Acquire没有分发即将退役和删除的东西

我拿了他的代码,试着让它运行。它充满了错误。我想我已经把它修好了,但是我不确定我是否用boost::atomic正确地替换了他的CAS函数

#pragma once

#include <iostream>

#include <unordered_map>
#include <vector>
#include <atomic>


typedef std::unordered_map<std::string, std::string> StringMap;


// Hazard pointer record
class HPRecType {

    //boost::atomic<int> active_;
    std::atomic_int active_;
    // Global header of the HP list
    static std::atomic<HPRecType *> pHead_;
    // The length of the list
    static std::atomic_int listLen_;
public:
    // Can be used by the thread
    // that acquired it
    void * pHazard_;
    HPRecType * pNext_;
    static HPRecType * Head() {
        return pHead_;
    }

    // Acquires one hazard pointer
    static HPRecType * Acquire() {
        int one = 1;
        // Try to reuse a retired HP record
        HPRecType * p = pHead_;
        for (; p; p = p->pNext_) {
            if (p->active_ || !p->active_.compare_exchange_strong(one, 0, std::memory_order_acquire))
                continue;
            // Got one!
            return p;
        }

        // Increment the list length
        int oldLen;
        do {
            oldLen = listLen_;

        } while (!listLen_.compare_exchange_strong(oldLen, oldLen +1, std::memory_order_acquire));

        // Allocate a new one
        p = new HPRecType;
        p->active_ = 1;
        p->pHazard_ = 0;
        // Push it to the front
        HPRecType * old;
        do {
            old = pHead_;
            p->pNext_ = old;
        } while (!pHead_.compare_exchange_strong(old, p, std::memory_order_acquire));
        return p;
    }

    // Releases a hazard pointer
    static void Release(HPRecType* p) {
        p->pHazard_ = 0;
        p->active_ = 0;
    }
};

// Per-thread private variable
//static boost::thread_specific_ptr<std::vector<StringMap*>> rlist;
static std::vector<StringMap*> *rlist;


class HazardPointerMap {
    std::atomic<StringMap *> pMap_;


private:
    static void Retire(StringMap * pOld) {
        // put it in the retired list
        rlist->push_back(pOld);
        if (rlist->size() >= 10) {
            Scan(HPRecType::Head());
        }
    }


    static void Scan(HPRecType * head) {
        // Stage 1: Scan hazard pointers list
        // collecting all non-null ptrs
        std::vector<void*> hp;
        while (head) {
            void * p = head->pHazard_;
            if (p) hp.push_back(p);
            head = head->pNext_;
        }
        // Stage 2: sort the hazard pointers
        sort(hp.begin(), hp.end(), std::less<void*>());
        // Stage 3: Search for'em!
        std::vector<StringMap *>::iterator i = rlist->begin();
        while (i != rlist->end()) {
            if (!binary_search(hp.begin(), hp.end(),  *i)) {
                // Aha!
                delete *i;
                i = rlist->erase(i);

                if (&*i != &rlist->back()) {
                    *i = rlist->back();
                }
                rlist->pop_back();
            } else {
                ++i;
            }
        }
    }

public:
    void Update(std::string&k, std::string&v){
        StringMap * pNew = 0;
        StringMap * pOld;
        do {
            pOld = pMap_;
            if (pNew) delete pNew;
            pNew = new StringMap(*pOld);
            (*pNew)[k] = v;
        } while (!pMap_.compare_exchange_strong(pOld, pNew, std::memory_order_acq_rel));
        Retire(pOld);
    }

    std::string Lookup(const std::string &k){
        HPRecType * pRec = HPRecType::Acquire();
        StringMap *ptr;
        do {
            ptr = pMap_;
            pRec->pHazard_ = ptr;
        } while (pMap_ != ptr);
        // Save Willy
        std::string result = ptr->at(k);
        // pRec can be released now
        // because it's not used anymore
        HPRecType::Release(pRec);
        return result;
    }

};
#pragma一次
#包括
#包括
#包括
#包括
typedef std::无序的_映射StringMap;
//危险指针记录
类HPRecType{
//boost::原子有源滤波器;
std::原子_int active_;
//HP列表的全局标题
静态std::原子pHead_u2;;
//名单的长度
静态标准::原子列表;
公众:
//可以由线程使用
//那就是它
无效*法扎德;
HPRecType*pNext\ux;
静态HPRecType*Head(){
返回pHead_;
}
//获取一个危险指针
静态HPRecType*Acquire(){
int-one=1;
//尝试重新使用失效的HP记录
HPRecType*p=pHead;
对于(;p;p=p->p下一页){
如果(p->active|||!p->active|比较交换强(1,0,std::memory_order_acquire))
继续;
//有一个!
返回p;
}
//增加列表长度
国际奥德伦;
做{
oldLen=listLen;
}而(!listLen.compare\u exchange\u strong(oldLen,oldLen+1,std::memory\u order\u acquire));
//分配一个新的
p=新的HPRecType;
p->active=1;
p->pHazard=0;
//把它推到前面
HPRecType*old;
做{
old=pHead_u2;;
p->pNext=旧的;
}而(!pHead.compare_exchange_strong(old,p,std::memory_order_acquire));
返回p;
}
//释放危险指针
静态无效释放(HPRecType*p){
p->pHazard=0;
p->active=0;
}
};
//每线程私有变量
//静态boost::特定于线程的ptr rlist;
静态std::vector*rlist;
类危险点映射{
std::原子pMap_;
私人:
静态无效失效(StringMap*pOld){
//把它放在退休名单上
rlist->push_back(pOld);
如果(rlist->size()>=10){
扫描(HPRecType::Head());
}
}
静态无效扫描(HPRecType*head){
//第1阶段:扫描危险指示器列表
//收集所有非空PTR
std::载体hp;
while(head){
void*p=head->pHazard;
如果(p)hp.推回(p);
头=头->下一页;
}
//阶段2:对危险指针进行排序
排序(hp.begin()、hp.end()、std::less());
//第三阶段:搜索他们!
std::vector::迭代器i=rlist->begin();
而(i!=rlist->end()){
如果(!二进制搜索(hp.begin(),hp.end(),*i)){
//啊哈!
删除*i;
i=rlist->erase(i);
如果(&*i!=&rlist->back()){
*i=rlist->back();
}
rlist->pop_back();
}否则{
++一,;
}
}
}
公众:
无效更新(标准::字符串和k,标准::字符串和v){
StringMap*pNew=0;
StringMap*pOld;
做{
pOld=pMap_2;;
如果(pNew)删除pNew;
pNew=新的StringMap(*pOld);
(*pNew)[k]=v;
}而(!pMap.compare_exchange_strong(pOld、pNew、std::memory_order_acq_rel));
退休(pOld);
}
std::string查找(const std::string&k){
HPRecType*pRec=HPRecType::Acquire();
StringMap*ptr;
做{
ptr=pMap;
pRec->pHazard=ptr;
}while(pMap_!=ptr);
//救救威利
std::string result=ptr->at(k);
//pRec现在可以发布了
//因为它不再使用了
HPRecType::Release(pRec);
返回结果;
}
};
我看不出Acquire在做什么。初始地图是如何绘制的 最后出现在危险指针列表上?也许有一些代码 失踪

Acquire()
不会将任何地图绑定到危险指针列表。它只是在列表中占了一个位置。映射绑定到
Acquire()
之后的
Lookup()
中该插槽的
pHazard
。注意行
pRec->pHazard\uuu=ptr如下所示

V Lookup(const K&k){
   HPRecType * pRec = HPRecType::Acquire();
   Map<K, V> * ptr;
   do {
      ptr = pMap_;
      pRec->pHazard_ = ptr;    //<---- Bind the map to slot here
   } while (pMap_ != ptr);
   // Save Willy
   V result = (*ptr)[k];
   // pRec can be released now
   // because it's not used anymore
   HPRecType::Release(pRec);
   return result;
}

另一件值得一提的事情是,
active\uuu
仅在读卡器之间使用,因此在单链接列表(共享危险指针列表)中,最多只能有一个读卡器获得相同的插槽。这可能会让您感到困惑,因为除此之外还使用了
pHazard
,两者都提供了某种保护(在读者之间,或读者和作者之间)。依我看,
active\uu
可以被删除,我们只需检查
pHazard
是否为空即可。唯一的问题是后者是一个可能是64位的指针,并且并非所有平台上都有64位的CAS原语。这就是作者试图回避的原因。

你还有问题吗?
Line 1:    do {
Line 2:       ptr = pMap_;
Line 3:       pRec->pHazard_ = ptr;
Line 4:    } while (pMap_ != ptr);