C++11实现基于循环数组的自旋锁队列LockFreeArrayQueue

C++11实现基于循环数组的无锁队列LockFreeArrayQueue

无锁队列

无锁队列一般指的是通过CAS操作来保证队列的线程安全性问题,而不会使得线程陷入到内核,以避免用户态与内核态的切换开销;

实现原理

  1. 采用循环数组,实现基于CAS自旋锁的有界队列;
  2. 自旋锁方式,对front/rear自旋为Exclude 表示成功获取自旋锁:
    2.1. 在push函数中,对rear成功CAS为Exclude 表示当前线程获取rear自旋锁成功,并需要判断当前数组是否已满,如果已满,则解锁继续自旋;否则成功,则push进入数据;最后解锁
    2.2. 在pop函数中,对front成功CAS为Exclude 表示当前线程获取front自旋锁成功,并需要判断当前数组是否为空,如果为空,则解锁继续自旋;否则成功,则pop出数据;最后解锁
  3. 避让策略:
    3.1 ABANDON表示当前操作允许失败;
    3.2 FORCE表示当前操作必须成功;
    3.3 YIELD表示当前操作必须成功,但允许调用yield,避免过度竞争;
  4. 为什么这里一定需要_size变量记录当前队列中元素的数量;
    在正常循环队列中,可以简单地通过将数组空出一个额外元素的方式,仅利用front和rear实现简单的循环队列。但是在本文提供的无锁队列中,front和rear被用来加锁了,即可能被设置为Exclude ,因此无法直接使用front和rear判断当前的元素数量;
  5. T应当是trival的,是否lock_free依赖于std::atomic是否lock_free;
  6. 存在ABA问题,但不会影响线程安全性;

源码

#pragma once
#include<vector>
#include<atomic>
#include<thread>
#include<assert.h>
using namespace std;

//保证T应当是trival
//基于循环数组的有界无锁队列
template<typename T>
class LockFreeArrayQueue {
public:
	//push or pop 避让策略
	enum class Strategy {
		ABANDON, //放弃
		FORCE,  //必须得到
		YIELD,  //必须得到,但会调用yield
	};
public:
	//保证初始化在单线程下完成
	LockFreeArrayQueue(int capacity):_data(capacity), _capacity(capacity){
		islockfree = capacity == 0 || _data.front().is_lock_free();
	}
	~LockFreeArrayQueue() {}

	bool is_lock_free() {
		return islockfree;
	}

	bool isFull() { return _size.load() == _capacity; }
	bool isEmpty() { return _size.load() == 0; }

	bool push(T val, Strategy strategy = Strategy::FORCE);
	bool pop(T& val, Strategy strategy = Strategy::FORCE);

private:
	const T Empty = 0;  //元素为0时,表示为空
	const int Exclude = -1;  //front or rear为-1时,表示其他线程已加锁,正在操作数据

	const int _capacity;  //数组最大容量
	std::vector<atomic<T>>_data;
	std::atomic<int>_size = { 0 };  //当前size
	std::atomic<int>_front = { 0 };  //头指针
	std::atomic<int>_rear = { 0 };   //尾指针
	bool islockfree;
};

template<typename T>
bool LockFreeArrayQueue<T>::push(T val, Strategy strategy) {
	int rear = _rear.load();
	while (true) {
		if (rear == Exclude || isFull()) {
			switch (strategy) {
			case Strategy::YIELD:
				std::this_thread::yield();
			case Strategy::FORCE:
				rear = _rear.load();
				continue;
			}
			return false;
		}
		//加rear锁
		if (_rear.compare_exchange_weak(rear, Exclude)) {
			//已满,失败解锁回退
			if (isFull()) {
				int excepted = Exclude;
				bool flag = _rear.compare_exchange_weak(excepted, rear);
				assert(flag);
				continue;
			}
			break;
		}
	}
	_data[rear].store(val);
	++_size; //必须在解锁前面
	int excepted = Exclude;
	//释放rear锁
	bool flag = _rear.compare_exchange_weak(excepted, (rear + 1) % _capacity);
	assert(flag);
	return true;
}

template<typename T>
bool LockFreeArrayQueue<T>::pop(T& val, Strategy strategy) {
	int front = _front.load();
	while (true) {
		if (front == Exclude || isEmpty()) {
			switch (strategy) {
			case Strategy::YIELD:
				std::this_thread::yield();
			case Strategy::FORCE:
				front = _front.load();
				continue;
			}
			return false;
		}
		//加锁
		if (_front.compare_exchange_weak(front, Exclude)) {
			//空,失败解锁回退
			if (isEmpty()) {
				int excepted = Exclude;
				bool flag = _front.compare_exchange_weak(excepted, front);
				assert(flag);
				continue;
			}
			break;
		}
	}
	val = _data[front].load();
	//_data[front].store(Empty);
	--_size; //必须在解锁前面
	int excepted = Exclude;
	bool flag = _front.compare_exchange_weak(excepted, (front + 1) % _capacity);
	assert(flag);
	return true;
}

测试代码

  1. 队列长度为10;
  2. 采用100个生产者线程,每个线程放入batch*num=10000的数据;
  3. 采用100个消费者线程;
#include"LockFreeArrayQueue.h"
#include<vector>
#include<unordered_map>
#include<mutex>
#include<condition_variable>
#include<thread>
#include<iostream>
#include <ctime>
using namespace std;

std::mutex mx;
condition_variable cond;
bool running = false;
atomic<int>cnt = 0;

//队列
LockFreeArrayQueue<int> queue(10);

const int pn = 100, cn = 100;  //生产者/消费者线程数
//每个生产者push batch*num的数据
const int batch = 100;  
const int num = 100;
unordered_map<int, int> counts[cn];

//生产者
void produce() {
	{
		std::unique_lock<std::mutex>lock(mx);
		cond.wait(lock, []() {return running; });
	}
	for (int i = 0; i < batch; ++i) {
		for (int j = 1; j <= num; ++j)
			queue.push(j);
	}
	++cnt;
}

//消费者
void consume(int i) {
	unordered_map<int, int>& count = counts[i];
	{
		std::unique_lock<std::mutex>lock(mx);
		cond.wait(lock, []() {return running; });
	}
	int val;
	while (true) {
		bool flag = queue.pop(val, LockFreeArrayQueue<int>::Strategy::ABANDON);
		if (flag) ++count[val];
		else if (cnt == pn) break;
	}

}
int main() {
	vector<thread>pThreads, cThreads;
	for (int i = 0; i < pn; ++i)
		pThreads.push_back(thread(&produce));
	for (int i = 0; i < cn; ++i) {
		cThreads.push_back(thread(&consume, i));
	}
	auto start = time(NULL);
	{
		std::lock_guard<std::mutex>guard(mx);
		running = true;
		cond.notify_all();
	}
	for (int i = 0; i < pn; ++i) pThreads[i].join();
	for (int i = 0; i < cn; ++i) cThreads[i].join();
	auto end = time(NULL);

	//结果统计
	unordered_map<int, int> res;
	for (auto& count : counts) {
		for (auto& kv : count) {
			res[kv.first] += kv.second;
		}
	}
	int total = 0;
	int failed = 0;
	for (auto& kv : res) {
		total += kv.second;
		cout << kv.first << " " << kv.second << endl;
		failed = !(kv.first > 0 && kv.first <= num && kv.second == batch * pn);
	}
	cout << "is_lock_free: " << (queue.is_lock_free() ? "true" : "false") << endl;
	cout << "consume time: " << end - start <<" seconds"<< endl;
	cout << "total numbers: " << total << endl;
	cout << "failed: " << failed << endl;
}

运行结果

is_lock_free: true
consume time: 36 seconds
total numbers: 1000000
failed: 0

码云链接

LockFreeArrayQueue.h
LockFreeArrayQueueTest.cpp

评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值