CAS lockfree 循环队列
来源:互联网 发布:400电话网站模版源码 编辑:程序博客网 时间:2024/06/05 17:37
在写网络爬虫时涉及到多线程并行处理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;}elsethis_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 中, 再次重复以上过程.
- CAS lockfree 循环队列
- CAS lockfree 循环队列
- 多线程基础(单读、单写、循环队列、无锁、lockfree)
- 实现一个lockfree 的队列
- lockfree 的队列的实现
- 线程安全的skiplist,lockfree,CAS,c11版
- 实现一个lockfree的队列——错误修改
- lock和lockfree 之内核如何使用队列的
- 无锁编程[2]__一读一写无锁队列,其实就是循环队列,一写多读(基于计数器和更新开关),基于CAS实现:多读多写无锁循环队列
- cas 循环数组
- 循环队列
- 循环队列
- 循环队列
- 循环队列
- 循环队列
- 循环队列
- 循环队列
- 循环队列
- 可以预见的未来(连续剧-1)
- PHP字符串的编码问题
- 南阳理工OJ第69题 阶乘位数
- 腾讯微信技术架构
- PHP异常处理详解
- CAS lockfree 循环队列
- PHP的类自动加载机制
- Ubuntu运行Chrome出现“Google Chrome can not be run as root”的解决方法
- 新浪微博技术架构
- 1.pdm的那些事-怎样让PDM图形显示name和code?
- 深入理解PHP的引用(References in PHP)
- 拼接树结构
- PHP中引用的详解(引用计数、写时拷贝)
- PHP5.4最新特性