C++11实现基于链表的无锁队列LockFreeLinkedQueue
- 无锁队列
- 实现原理
- 源码
- 测试代码
- 运行结果
- 码云链接
无锁队列
无锁队列一般指的是通过CAS操作来保证队列的线程安全性问题,而不会使得线程陷入到内核,以避免用户态与内核态的切换开销;
实现原理
- 采用链表,实现基于自旋锁CAS的无界队列
- 自旋锁方式,对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; - 为什么这里一定需要count变量记录当前队列中元素的数量?
head和tail被用来进行加锁,可以临时设置为NULL,表示加锁,这时候head和tail可能不会指向的应该指向节点,因此。需要count计数器复辅助判断当前队列中的元素; - T应当是trival的,是否lock_free依赖于std::atomic是否lock_free;
- 存在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;
}
测试代码
- 采用100个生产者线程,每个线程放入batch*num=10000的数据;
- 采用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.hLockFreeLinkedQueueTest.cpp