前言
本文介绍了无锁队列的实现方式
一、无锁队列
我们首先使用一个原子变量实现了无锁队列,随后为了提高并发度,采用三个原子变量进行了实现
1、使用一个原子变量的实现
该实现仅支持同一时刻仅有一个线程对队列进行push或者pop
#include <iostream>
#include <string.h>
#include <atomic>
using namespace std;
const int MAXN = 1010;
class Queue {
public:
Queue(int len) : maxsize(len + 1), atomic_using(false) {
memset(que, 0, sizeof(que));
start = 0;
end = 0;
}
bool EnQueue(int value) {
// 初始时atomic_using为false, 因此第一个进入do的线程能够返回true跳出循环,之后的会不断循环检查
bool use_expected = false;
bool use_desired = true;
do {
use_expected = false;
use_desired = true;
} while (!atomic_using.compare_exchange_strong(use_expected, use_desired));
// 队列为满,直接返回
if ((end + 1) % maxsize == start) {
do {
use_expected = true;
use_desired = false;
} while (!atomic_using.compare_exchange_strong(use_expected, use_desired));
return false;
}
// 加入元素并更新指针
que[end] = value;
end = (end + 1) % maxsize;
do {
use_expected = true;
use_desired = false;
} while (!atomic_using.compare_exchange_strong(use_expected, use_desired));
return true;
}
bool DeQueue() {
bool use_expected = false;
bool use_desired = true;
do {
use_expected = false;
use_desired = true;
} while (!atomic_using.compare_exchange_strong(use_expected, use_desired));
// 队列为空,直接返回
if (start == end) {
do {
use_expected = true;
use_desired = false;
} while (!atomic_using.compare_exchange_strong(use_expected, use_desired));
return false;
}
start = (start + 1) % maxsize;
do {
use_expected = true;
use_desired = false;
} while (!atomic_using.compare_exchange_strong(use_expected, use_desired));
return true;
}
int que[MAXN];
atomic<bool> atomic_using;
int start, end;
int maxsize;
};
int main()
{
Queue *que = new Queue(3);
cout << que->EnQueue(0) << endl;
cout << que->EnQueue(1) << endl;
cout << que->EnQueue(2) << endl;
cout << que->DeQueue() << endl;
cout << que->DeQueue() << endl;
return 0;
}
2、使用三个原子变量
支持多个线程同时对队列进行push与pop操作
#include <iostream>
#include <string.h>
#include <atomic>
using namespace std;
const int MAXN = 1010;
/*
前一个版本只用了一个原子变量,虽然可以保证线程安全,但是
push操作和pop操作的耦合性过高,同一时刻只能有一个线程pop或者push;
事实上,一个线程在push的时候,可能并不会影响另一个线程pop,为了提高并发
,引入两个变量使得能够同时进行push和pop操作;
但是,当一个线程push时,第一步需要移动end指针,第二步才将元素放入队列中,
可能带来的隐患就是一个线程执行完第一步之后,另一个线程开始pop,造成pop出来的元素
与应当push(实际还未push)的不一致。因此,需要引入第三个变量保证其一致性
*/
class Queue {
public:
Queue(int len) : maxsize(len + 1) {
memset(que, 0, sizeof(que));
start = 0;
end = 0;
last = 0;
}
bool EnQueue(int value) {
size_t e;
do {
// 取出队首指针
e = end.load();
// 判断队列是否为满
if ((e + 1) % maxsize == start.load()) {
return false;
}
} while (!end.compare_exchange_strong(e, (e + 1) % maxsize));
// 元素入队
que[e] = value;
/*
第二个cas操作保证赋值顺序与指针移动顺序一致,只有当先前移动过指针的线程
将元素放入队列中后,后面的线程才能继续将元素放入后面的空间
*/
size_t l;
do {
l = e;
} while (last.compare_exchange_strong(l, (l + 1) % maxsize));
return true;
}
bool DeQueue() {
size_t s;
do {
s = start.load();
// 队列为空,直接返回
if (s == end.load()) {
return false;
}
// 还需要判断,push线程是否真的把元素写入到队列了,如果仅仅只是移动了指针,那么就不能pop
if (s == last.load()) {
return false;
}
// 此处可以读取数据
} while (!start.compare_exchange_strong(s, (s + 1) % maxsize));
return true;
}
int que[MAXN];
atomic<size_t> start, end, last;
int maxsize;
};
int main()
{
Queue *que = new Queue(3);
cout << que->EnQueue(0) << endl;
cout << que->EnQueue(1) << endl;
cout << que->EnQueue(2) << endl;
cout << que->DeQueue() << endl;
cout << que->DeQueue() << endl;
return 0;
}
参考文章
1、无锁并发队列