CAS lockfree 循环队列

在写网络爬虫时涉及到多线程并行处理URL的问题, 开始打算给相关数据加锁来解决该问题, 之后考虑到锁是会影响性能的, 虽然处理URL的那部分不是这种小型爬虫的瓶颈所在(网速才 是最大的瓶颈啊), 但能更快一点岂不更好? 所以就想使用无锁技术.

通过查阅资料, 参考陈皓老师的无锁队列的实现 和淘宝搜索技术博客的一种高效无锁内存队列的实现, 使用CAS(compare and swap, 比较交换)技术和数组模拟实现无锁队列.

CAS 操作是在一个原子操作内完成的.

使用 CAS 需要提供三个参数, 分别为:

要修改的变量var;
存储在执行 CAS 操作之前变量 var 的原值 old_var;
变量 var 的目的值 new_var.

CAS 操作处理过程:

  • 在执行 CAS 操作前, 需要先获取变量 var 的值并存储到 old_var 中.
  • 之后执行 CAS 操作: 检查 var 是否与 old_var 相等. 如果 var == old_var, 则说明没有其它线程修改 var, 此时可将 new_var 赋值给 var. 如果 var != old_var, 则说明在当前线程保存 var 的值后有其它线程修改了变量 var, 此时应重新获取变量 var 的值并保存至 old_var 中, 再次重复以上过程.

CAS的ABA问题主要出现在动态分配内存的情形, 使用数组模拟实现无锁队列时, 每一个存储空间都是固定不变的, 所以就不需要考虑这个问题了.

使用CAS技术和数组模拟实现无锁队列时需要注意的问题:

* 利用数组模拟循环队列时, 数组大小是固定的, 进队和出队操作要齐头并进, 不能存在较大时间差. 如果有一段时间只入队而不出队, 则在队列满之后, 入队操作将会被阻塞, 类似于死锁. 所以当出入队时间存在较大时间差时可考虑动态分配存储空间. * 利用数组模拟循环队列时, 通过取余操作定位下标的效率较低. 可将数组大小设置为2的指数倍, 之后通过位操作确定下标, 如 index & (size - 1) 的形式定位数组下标.

以下代码是我用C++实现的无锁队列模板, 提供如下接口:

Enqueue: 入队
Dequeue: 出队
set_enqueue_done: 设置入队完毕标识
get_is_enqueue_done: 检查是否入队完毕
get_is_denqueue_done: 检查是否出队完毕
get_enqueue_num: 获得最新的入队编号
get_dequeue_num: 获取最新的出队编号

代码:

// 采用 CAS 技术, 用数组实现的无锁队列,
// 可多线程入队出队.

#ifndef _QUEUE_H_
#define _QUEUE_H_

#include <atomic>
#include <boost/thread.hpp>

using namespace std;
using namespace boost;

template<typename T>
class Queue
{
	public:
		Queue(const int &size = 16384);
		~Queue() { delete [] m_data; }

		long Enqueue(const T &value);
		long Dequeue(T &value);

		void set_is_enqueue_done(const bool &is_enqueue_done)
			{ m_is_enqueue_done = is_enqueue_done; }

		bool get_is_enqueue_done() const { return m_is_enqueue_done; }
		bool get_is_dequeue_done() const { return m_is_dequeue_done; }
		long get_dequeue_num() const { return m_dequeue_num; }
		long get_enqueue_num() const { return m_enqueue_num; }
		void Clear();

	private:
		int m_size;
		bool m_is_enqueue_done;
		bool m_is_dequeue_done;

		volatile atomic<long> m_enqueue_num;
		volatile atomic<long> m_dequeue_num;
		T *m_data;

		void set_size(const int &size);
};

template<typename T>
Queue<T>::Queue(const int &size /* = 16384 */)
{
	set_size(size);
	m_data = new T[m_size + 1];

	m_is_enqueue_done = m_is_dequeue_done = false;
	m_enqueue_num = m_dequeue_num = 0;
}

template<typename T>
void Queue<T>::set_size(const int &size)
{
    if (size <= 16384)
	{
		m_size = 16384;
		return;
	}

	m_size  = 16384;
	while (m_size < size)
	{
		m_size <<= 1;
	}
    m_size >>= 1;
}

template<typename T>
long Queue<T>::Enqueue(const T &value)
{
	while (m_enqueue_num - m_dequeue_num >= m_size)
		this_thread::sleep(posix_time::seconds(1)); // this_thread::yield();

    long old_num;
	do
	{
enqueue_loop:
		old_num = m_enqueue_num;
		if (-1 == m_enqueue_num)
		{
			this_thread::sleep(posix_time::seconds(1)); // this_thread::yield();
			goto enqueue_loop;
		}

	} while (!atomic_compare_exchange_weak(&m_enqueue_num, &old_num, (long)-1));
	m_data[old_num & (m_size - 1)] = value;
    m_enqueue_num = old_num + 1;
	return m_enqueue_num;
}

template<typename T>
long Queue<T>::Dequeue(T &value)
{
	long old_num_, new_num;
	do
	{
dequeue_loop:
		old_num_ = m_dequeue_num;
		new_num= old_num_ + 1;
		if (m_dequeue_num >= m_enqueue_num)
		{
			if (m_is_enqueue_done)
			{
				m_is_dequeue_done = true;
				return 0;
			}
			else
				this_thread::sleep(posix_time::seconds(1)); // this_thread::yield();
			goto dequeue_loop;
		}
		value = m_data[m_dequeue_num & (m_size - 1)];
	} while (!atomic_compare_exchange_weak(&m_dequeue_num, &old_num_, new_num));
	return m_dequeue_num;
}


template<typename T>
void Queue<T>::Clear()
{
	m_enqueue_num = m_dequeue_num = 0;
	m_is_enqueue_done = m_is_dequeue_done = false;
}

#endif	/* _QUEUE_H_ */
总结:1.循环队列使用线性表实现,少用一个节点区分空和满的情况,队头和队尾等为空,(队尾+1)%size==队头为满。
取余操作效率较低,可将数组大小设置为2的指数倍,可通过index&(size-1)形式定位数组下标。
2.lockfree关键是保障队头队尾指针atomic

使用 CAS 需要提供三个参数, 分别为:

要修改的变量var;
存储在执行 CAS 操作之前变量 var 的原值 old_var;
变量 var 的目的值 new_var.

CAS 操作处理过程:

  • 在执行 CAS 操作前, 需要先获取变量 var 的值并存储到 old_var 中.
  • 之后执行 CAS 操作: 检查 var 是否与 old_var 相等. 如果 var == old_var, 则说明没有其它线程修改 var, 此时可将 new_var 赋值给 var. 如果 var != old_var, 则说明在当前线程保存 var 的值后有其它线程修改了变量 var, 此时应重新获取变量 var 的值并保存至 old_var 中, 再次重复以上过程.
转载自:http://www.cnfn.org/cas-no-lock-queue.html
  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值