无锁队列
无锁队列一般指的是通过CAS操作来保证队列的线程安全性问题,而不会使得线程陷入到内核,以避免用户态与内核态的切换开销;
实现原理
- 采用循环数组,实现基于CAS自旋锁的有界队列;
- 自旋锁方式,对front/rear自旋为Exclude 表示成功获取自旋锁:
2.1. 在push函数中,对rear成功CAS为Exclude 表示当前线程获取rear自旋锁成功,并需要判断当前数组是否已满,如果已满,则解锁继续自旋;否则成功,则push进入数据;最后解锁
2.2. 在pop函数中,对front成功CAS为Exclude 表示当前线程获取front自旋锁成功,并需要判断当前数组是否为空,如果为空,则解锁继续自旋;否则成功,则pop出数据;最后解锁 - 避让策略:
3.1 ABANDON表示当前操作允许失败;
3.2 FORCE表示当前操作必须成功;
3.3 YIELD表示当前操作必须成功,但允许调用yield,避免过度竞争; - 为什么这里一定需要_size变量记录当前队列中元素的数量;
在正常循环队列中,可以简单地通过将数组空出一个额外元素的方式,仅利用front和rear实现简单的循环队列。但是在本文提供的无锁队列中,front和rear被用来加锁了,即可能被设置为Exclude ,因此无法直接使用front和rear判断当前的元素数量; - T应当是trival的,是否lock_free依赖于std::atomic是否lock_free;
- 存在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;
}
测试代码
- 队列长度为10;
- 采用100个生产者线程,每个线程放入batch*num=10000的数据;
- 采用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