关于ABA问题的方案

无锁设计主要存在的问题

会出现ABA问题。

解决该问题的方式:

  1. 使用tag来进行标识该节点是否被其它线程获取过,这个tag与指针结合起来一般是128bit,需要处理器的支持,目前x86 cpu是不支持的
  2. 使用风险指针,但是实际跟加上一个锁差不多,特别是对于同一个节点的时候,因为只有再这个节点pop完之后才能让其它线程进行pop操作。
  3. 使用RCU技术,这种方式是通过所有线程的pop操作完之后,再进行push的操作,这样可能会出现资源耗尽的问题。
  4. 使用一个中心节点来进行统一的pop操作和push操作,缺点:效率低。

实现没有线程数量限制的hazard point功能


template<typename T>
struct registerThreadToQueue
{
    hazardPointerContainer<T>* localContainer_;
    registerThreadToQueue():
        localContainer_(nullptr)
    {
        auto tempContainerIndex = hazardPointerQueue<T>::getHeader().next_.load(std::memory_order_acquire);
        if(!tempContainerIndex)
        {
            this->insertHazardPointQueue();
        }
        else
        {
            constexpr int maxLoopTimes = 100;
            for(int i = 0; i < maxLoopTimes; ++i)
            {
                for(; !tempContainerIndex;)
                {
                    std::thread::id defaultThreadId;
                    if(tempContainerIndex->thread_id_.load(std::memory_order_acquire ) == defaultThreadId)
                    {
                        if(tempContainerIndex->thread_id_.compare_exchange_strong(defaultThreadId, std::this_thread::get_id()))
                        {
                            this->localContainer_ = tempContainerIndex;
                            return;
                        }
                    }
                    tempContainerIndex = tempContainerIndex->next_;
                }
            }
            this->insertHazardPointQueue();
        }
    }

    void insertHazardPointQueue()
    {
        auto tempContainerIndex = hazardPointerQueue<T>::getHeader().next_.load(std::memory_order_acquire );
        auto newContainer = new hazardPointerContainer<T>();
        while (1)
        {
            //When initializing hazard pointer queue, there are only push operations.
            newContainer->next_ = tempContainerIndex;
            if(hazardPointerQueue<T>::getHeader().next_.compare_exchange_weak(tempContainerIndex, newContainer))
            {
                this->localContainer_ = newContainer;
                break;
            }
        }
    }

    ~registerThreadToQueue()
    {
        localContainer_->resetContainer();
    }
};

template<typename T>
struct hazardPointerContainer
{
    hazardPointerContainer<T>* next_;
    std::atomic<T> value_;
    std::atomic<std::thread::id> thread_id_;

    hazardPointerContainer():
        next_(nullptr),
        thread_id_(std::this_thread::get_id())
    {
    }
    hazardPointerContainer(T value) :
        value_(value),
        next_(nullptr),
        thread_id_(std::this_thread::get_id())
    {
    }

    void resetContainer()
    {
        // T tempValue;
        // std::thread::id tempThreadId;
        value_.exchange(T());
        thread_id_.exchange(std::thread::id());
    }

    void resetValue()
    {
        T tempValue;
        value_.exchange(tempValue);
    }

    ~hazardPointerContainer()
    {
        this->resetContainer();
    }
};

template<typename T>
struct hazardPointerContainerHeader_t
{
    hazardPointerContainerHeader_t() = default;
    ~hazardPointerContainerHeader_t() = default;
    std::atomic<hazardPointerContainer<T>*> next_;
};

template<typename T>
struct hazardPointerQueue
{
    hazardPointerQueue() = default;
    ~hazardPointerQueue();
    static hazardPointerContainerHeader_t<T>& getHeader();
    bool findConflictPointer(T value);
    bool setHazardPointer(T value);
    bool removeHazardPointer();
    hazardPointerContainer<T>* getThreadLocalContainer();
};

template<typename T>
hazardPointerQueue<T>::~hazardPointerQueue()
{
    auto headIndex = hazardPointerQueue::getHeader().next_.load(std::memory_order_acquire);
    if(headIndex != nullptr)
    {
        for(;headIndex != nullptr;)
        {
            auto tempIndex = headIndex;
            delete headIndex;
            headIndex = tempIndex->next_;
        }
    }
}

template<typename T>
hazardPointerContainerHeader_t<T>& hazardPointerQueue<T>::getHeader()
{
    static hazardPointerContainerHeader_t<T> header;
    return header;
}

template<typename T>
hazardPointerContainer<T>* hazardPointerQueue<T>::getThreadLocalContainer()
{
    thread_local registerThreadToQueue<T> registerContainer; //1
    return registerContainer.localContainer_;
}

template<typename T>
bool hazardPointerQueue<T>::setHazardPointer(T value)
{
    auto localContainer = this->getThreadLocalContainer();
    localContainer->value_.store(value, std::memory_order_release);
    return true;
}

/// @brief Find the pointer has been announced by other thread.
/// @tparam T 
/// @param value 
/// @return true: not found, false: found the hazard pointer
template<typename T>
bool hazardPointerQueue<T>::findConflictPointer(T value)
{
    for(auto containerIndex = hazardPointerQueue::getHeader().next_.load(std::memory_order_acquire); containerIndex != nullptr; containerIndex = containerIndex->next_)
    {
        if(containerIndex->thread_id_ != std::this_thread::get_id() && containerIndex->value_.load(std::memory_order_acquire) == value)
        {
            return false;
        }
    }
    return true;
}

template<typename T>
bool hazardPointerQueue<T>::removeHazardPointer()
{
    auto localContainer = this->getThreadLocalContainer();
    localContainer->resetValue();
    return true;
}

代码中定义了一个类registerThreadToQueue,这个类在创建registerThreadToQueue实例的时候,就会将风险指针注册到队列中,并且如果队列中没有可用的hazardPointerContainer的话,那么该类就会在队列中通过无锁的方式插入一个新的hazardPointerContaine。通过这种方式将风险指针队列自动增容。

在代码//1处,使用thread_local来指示registerThreadToQueue的实例存在线程局部变量中,使用thread_local关键字的好处可以实现RAII的用法,在新的线程第一次使用setHazardPointer函数的时候,可以获得属于该线程风险指针的位置,在线程销毁时可以释放风险指针的资源。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值