无锁栈的一种实现方式

本文中无锁栈的实现,基于对《c++并发编程实践》的学习,对其他无锁数据结构的实现也有借鉴意义。

先看无锁栈的最初版本:

template <typename T>
class lockfreestack
{
	struct node
	{
		std::unique_ptr<T> data;
		node *next;
	};
	std::atomic<node*> head{nullptr};
public:
	void push(T data)
	{
		node *new_node = new node;
		new_node->data = new T(std::move(data));
		new_node->next = head.load();
		while(!head.compare_exchange_weak(new_node->next, new_node));
	}
	std::unique_ptr<T> pop()
	{
		node *old_head = head.load();
		while(old_head && !head.compare_exchange_weak(old_head, old_head->next)); // I
		return old_head ? std::move(old_head->data) : nullptr;
	}
};

还不错,除了有内存泄露。在pop中,compare_exchange_weak成功的线程是不能直接delete old_head;的,因为其他线程可能拿着同样的指针,在取old_head->next。

很自然的想法,要是现在不能释放,那就先收集起来,等能释放的时候释放。
可能有问题的地方在于I的old_head->next,可以维护一个原子变量计数器,进入I之前加1,离开I后减1,当计数器值不为0时,就把要删除的指针存起来,当计数器为0时,就可以把当前的old_head以及之前暂存的指针一起删除掉了。实现细节可参考 chest 中的 lockfreestack_origin.h文件。

上面的计数方法,粒度有点大,处于I处的线程,可能拿到的不是同样的指针,但会导致所有的待删除指针都无法释放。
这里介绍一种风险指针的方法。考虑到每个线程最多只能占用着一个old_head,可以记下每个线程占用着的指针,当想要释放一个指针时,看一下是否有其他线程在占用这个指针,如果没有的话,就可以释放掉。可参考 chest 中的 lockfreestack2.h文件。

第二种方法粒度细一些,可以及时的释放掉内存,但增加了一些计算,可能会得不偿失。

你可能感兴趣的:(c++11,c++)