c++ 高并发队列的实现

JAVA如何进行CAS

讲到java的队列时,讲到java中的CAS操作
回顾下java中的cas,主要采用compareAndSet方法,如AtomicReference中所使用的:
AtomicRefrence.java

/**
     * Atomically sets the value to the given updated value
     * if the current value {@code ==} the expected value.
     * @param expect the expected value
     * @param update the new value
     * @return {@code true} if successful. False return indicates that
     * the actual value was not equal to the expected value.
     */
    public final boolean compareAndSet(V expect, V update) {
   
        return unsafe.compareAndSwapObject(this, valueOffset, expect, update);
    }

unsafe.java


    /**
     * Atomically update Java variable to x if it is currently
     * holding expected.
     * @return true if successful
     */
    public final native boolean compareAndSwapObject(Object o, long offset,
                                                     Object expected,
                                                     Object x);
  • obj :包含要修改的字段对象;
  • offset :字段在对象内的偏移量;
  • expect : 字段的期望值;
  • update : 如果该字段的值等于字段的期望值,用于更新字段的新值;

compareAndSwapObject是一个本地方法,调用的c++的实现

// Unsafe.h
virtual jboolean compareAndSwapObject(::java::lang::Object *, jlong, ::java::lang::Object *, ::java::lang::Object *);

// natUnsafe.cc
static inline bool
compareAndSwap (volatile jobject *addr, jobject old, jobject new_val)
{
   
	jboolean result = false;
	spinlock lock;
  
  	// 如果字段的地址与期望的地址相等则将字段的地址更新
	if ((result = (*addr == old)))
    	*addr = new_val;
	return result;
}

// natUnsafe.cc
jboolean
sun::misc::Unsafe::compareAndSwapObject (jobject obj, jlong offset,
                     jobject expect, jobject update)
{
   
	// 获取字段地址并转换为字符串
	jobject *addr = (jobject*)((char *) obj + offset);
	// 调用 compareAndSwap 方法进行比较
    return compareAndSwap (addr, expect, update);
}

这段代码主要做的是:
1、通过对象的首地址跟字段在对象内的偏移量来获取字段的地址
2、判断字段地址是否与我们期望的地址相同,如果相同即更新新的地址

在java中,地址相同说明两个对象相同。

注意到c++源码中使用的 spinlock,其实现:


// Use a spinlock for multi-word accesses
class spinlock
{
   
  static volatile obj_addr_t lock;

public:

spinlock ()
  {
   
    while (! compare_and_swap (&lock, 0, 1))
      _Jv_ThreadYield ();
  }
  ~spinlock ()
  {
   
    release_set (&lock, 0);
  }
};
  
// This is a single lock that is used for all synchronized accesses if
// the compiler can't generate inline compare-and-swap operations.  In
// most cases it'll never be used, but the i386 needs it for 64-bit
// locked accesses and so does PPC32.  It's worth building libgcj with
// target=i486 (or above) to get the inlines.
volatile obj_addr_t spinlock::lock;

volatile关键字让编译器不进行优化,从而lock值每次都从内存中读取。

C++如何进行CAS

通过前半文的了解,我们知道c 提供的函数 compare_and_swap

bool compare_and_swap ( int *memory_location, int expected_value, int new_value)
{
   
    if (*memory_location == expected_value)
    {
   
        *memory_location = new_value;
        return true;
    }
    return false;
}

(1)GGC对CAS支持
GCC4.1+版本中支持CAS原子操作。

bool __sync_bool_compare_and_swap (type *ptr, type oldval type newval, ...);
type __sync_val_compare_and_swap (type *ptr, type oldval type newval, ...);

(2)Windows对CAS支持
Windows中使用Windows API支持CAS。

LONG InterlockedCompareExchange(
  LONG volatile *Destination,
  LONG          ExChange,
  LONG          Comperand
);

(3)C11对CAS支持
C11 STL中atomic函数支持CAS并可以跨平台。

template< class T >
bool atomic_compare_exchange_weak( std::atomic* obj,T* expected, T desired );

template< class T >
bool atomic_compare_exchange_weak( volatile std::atomic* obj,T* expected, T desired );

atomic_compare_exchange_weak当存储值时会发生失败情况,且返回false,所以通常采用while直至比对交换成功,相比使用aotmic_compare_exchange_strong会有更高的性能,而使用while也即是通常所说的自旋效果。

已有的c++ 无锁队列

1、生产级队列ConcurrencyQueue
这是我在生产中使用的,因为消费者可以采用wait方式监听队列消息进行消费,所以蛮方便的。

2、boost方案:
boost提供了三种无锁方案,分别适用不同使用场景。
boost::lockfree::queue是支持多个生产者和多个消费者线程的无锁队列。
boost::lockfree::stack是支持多个生产者和多个消费者线程的无锁栈。
boost::lockfree::spsc_queue是仅支持单个生产者和单个消费者线程的无锁队列,比boost::lockfree::queue性能更好。
Boost无锁数据结构的API通过轻量级原子锁实现lock-free,不是真正意义的无锁。
Boost提供的queue可以设置初始容量,添加新元素时如果容量不够,则总容量自动增长;但对于无锁数据结构,添加新元素时如果容量不够,总容量不会自动增长。

因为boost太大了,所以生产比较少用

自己造轮子

当我们能把一样东西做出来,说明我们才是真正的了解,所以多造轮子对自己有帮助。

RingBuffer 环形队列

c++ 高并发队列的实现_第1张图片

数据结构

我们采用数组的线性空间来实现环形接口,当数据到达尾部时将其转回到0的位置重写入。
环形结构的容量位置从数组q[0] 到 q[max - 1]。
head表示队列头,tail表示队列尾,当(tail + 1) % max 即表示队列已满

算法

%max 取余 可以 通过位运算  head &  (max - 1),需要保证max是2的幂次方

RingBuffer实现

单消费者,单生产者

#pragma once

template<typename T>
class RingBuffer {
   
private:
	//队列大小
	unsigned int _size;
	//队列头部索引
	int _front;
	//队列尾部索引
	int _tail;
	//数据缓冲区
	T* _data;
public:
	RingBuffer(unsigned int size) :_size(size),_front(0), _tail(0) {
   
		_data = new T[size]

你可能感兴趣的:(高并发服务器,C/C++,c++,java,内存队列,高并发服务器)