无锁设计主要存在的问题
会出现ABA问题。
解决该问题的方式:
- 使用
tag
来进行标识该节点是否被其它线程获取过,这个tag与指针结合起来一般是128bit,需要处理器的支持,目前x86 cpu是不支持的。 - 使用
风险指针
,但是实际跟加上一个锁差不多,特别是对于同一个节点的时候,因为只有再这个节点pop完之后才能让其它线程进行pop操作。 - 使用
RCU
技术,这种方式是通过所有线程的pop操作完之后,再进行push的操作,这样可能会出现资源耗尽的问题。 - 使用一个中心节点来进行统一的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函数的时候,可以获得属于该线程风险指针的位置,在线程销毁时可以释放风险指针的资源。