C++11实现基于链表的自旋锁队列LockFreeLinkedQueue

C++11实现基于链表的无锁队列LockFreeLinkedQueue

无锁队列

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

实现原理

  1. 采用链表,实现基于自旋锁CAS的无界队列
  2. 自旋锁方式,对head/tail自旋为NULL 表示成功获取自旋锁:
    2.1. 在push函数中,对tail成功CAS为NULL 表示当前线程获取tail自旋锁成功,并设置tail的next节点为push的元素,解锁tail,即将tail进行CAS为tail->next;
    2.2. 在tryPop函数中,对head成功CAS为NULL 表示当前线程获取head自旋锁成功,并需要判断当前数组是否为空,如果为空,则解锁并返回为false;否则成功,则pop出数据head->next->val,最后解锁,即将head进行CAS为head->next;
  3. 为什么这里一定需要count变量记录当前队列中元素的数量?
    head和tail被用来进行加锁,可以临时设置为NULL,表示加锁,这时候head和tail可能不会指向的应该指向节点,因此。需要count计数器复辅助判断当前队列中的元素;
  4. T应当是trival的,是否lock_free依赖于std::atomic是否lock_free;
  5. 存在ABA问题,但不会影响线程安全性;

源码

#pragma once
#include<iostream>
#include<atomic>
#include<thread>
#include<assert.h>

//保证T应当是trival
//基于链表的无界无锁队列
template<typename T>
class LockFreeLinkedQueue {
public:
	//保证初始化在单线程下完成
	LockFreeLinkedQueue() {
		Node* node = new Node(Empty);
		head.store(node);
		tail.store(node);
		islockfree = node->val.is_lock_free();
	}
	~LockFreeLinkedQueue() {
		T val = Empty;
		while (tryPop(val));
		Node* node = head.load();
		if (node != NULL)
			delete node;
	}
	bool is_lock_free() {
		return islockfree;
	}

	bool isEmpty() { return count.load() == 0; }
	bool isFull() { return false; }

	//push操作,CAS加tail锁
	bool push(T val);

	//pop操作,CAS加head锁
	bool tryPop(T& val);

	//不建议使用,当队列中无元素时,会自旋
	T pop();

private:
	struct Node {
		std::atomic<T> val;
		std::atomic<Node*>next = NULL;
		Node(T val) :val(val) {

		}
	};
	const T Empty = 0;
	std::atomic<int>count = { 0 };  //计数器
	std::atomic<Node*>head;  //头结点
	std::atomic<Node*>tail;   //尾结点
	bool islockfree;
};


//push操作,CAS加tail锁
template<typename T>
bool LockFreeLinkedQueue<T>::push(T val) {
	Node* t = NULL;
	Node* node = new Node(val);
	while (true) {
		//t==NULL,表示tail锁被抢
		if (t == NULL) {
			t = tail.load();
			continue;
		}
		//尝试加tail锁
		if (!tail.compare_exchange_weak(t, NULL))
			continue;
		break;
	}
	t->next.store(node);
	++count;
	Node* expected = NULL;
	//释放tail锁
	bool flag = tail.compare_exchange_weak(expected, t->next);
	assert(flag);
	return true;
}

//pop操作,CAS加head锁
template<typename T>
bool LockFreeLinkedQueue<T>::tryPop(T& val) {
	Node* h = NULL, * h_next = NULL;
	while (true) {
		//h==NULL,表示head锁被抢
		if (h == NULL) {
			h = head.load();
			continue;
		}
		//尝试加head锁
		if (!head.compare_exchange_weak(h, NULL))
			continue;
		h_next = h->next.load();
		//h->next != NULL 且 count == 0  
		//   此时在push函数中数据以及count计数器没有来得及更新,因此进行自旋
		if (h_next != NULL) {
			while (count.load() == 0)
				std::this_thread::yield();  //???
		}
		break;
	}
	Node* expected = NULL;
	Node* desired = h;
	//当h_next==NULL时
	//   表示当前链表为空
	if (h_next != NULL) {
		val = h_next->val;
		delete h;
		desired = h_next;
		--count;
	}
	//CAS head,释放head锁
	bool flag = head.compare_exchange_weak(expected, desired);
	assert(flag);
	return h_next != NULL;
}

//不建议使用,当队列中无元素时,会自旋
template<typename T>
T LockFreeLinkedQueue<T>::pop() {
	Node* h = NULL, * h_next = NULL;
	while (true) {
		//h==NULL,表示head锁被抢
		if (h == NULL) {
			h = head.load();
			continue;
		}
		//尝试加head锁
		if (!head.compare_exchange_weak(h, NULL))
			continue;
		h_next = h->next.load();
		//h->next == NULL 
		//   当前队列为空,是否需要解head锁?
		//h->next != NULL 且 count == 0  
		//   此时在push函数中数据以及count计数器没有来得及更新,因此进行自旋
		while (h_next == NULL || count.load() == 0) {
			std::this_thread::yield();  //???
			if (h_next == NULL)
				h_next = h->next.load();
		}
		break;
	}
	T val = h_next->val;
	delete h;
	--count;
	Node* expected = NULL;
	Node* desired = h_next;
	//CAS head,释放head锁
	bool flag = head.compare_exchange_weak(expected, desired);
	assert(flag);
	return val;
}

测试代码

  1. 采用100个生产者线程,每个线程放入batch*num=10000的数据;
  2. 采用100个消费者线程;
#include"LockFreeLinkedQueue.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;

//队列
LockFreeLinkedQueue<int> queue;

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.tryPop(val);
		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: 13 seconds
total numbers: 1000000
failed: 0

码云链接

LockFreeLinkedQueue.h
LockFreeLinkedQueueTest.cpp

  • 0
    点赞
  • 10
    收藏
  • 打赏
    打赏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
©️2022 CSDN 皮肤主题:大白 设计师:CSDN官方博客 返回首页
评论 1

打赏作者

xin_hen

你的鼓励将是我创作的最大动力

¥2 ¥4 ¥6 ¥10 ¥20
输入1-500的整数
余额支付 (余额:-- )
扫码支付
扫码支付:¥2
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值