网上学习资料一大堆,但如果学到的知识不成体系,遇到问题时只是浅尝辄止,不再深入研究,那么很难做到真正的技术提升。
一个人可以走的很快,但一群人才能走的更远!不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!
下面介绍一下atomic类型的两个函数操作
//取出当前atomic对象指向的值
T load (memory_order sync = memory_order_seq_cst) const volatile noexcept;
//如果内存中的值和参数一相同,那么替换为参数二,成功返回true
bool compare_exchange_weak (T& expected, T val,
memory_order sync = memory_order_seq_cst) volatile noexcept;
两个函数看起来很复杂,用起来其实超级简单,现在一起来看对无锁队列的插入代码块:
void Enqueue(const T& x)
{
Node\* newnode = new Node(x);//要插入的新值
Node\* oldtail = nullptr;//旧的尾节点
Node\* nullnode = nullptr;//空节点
do
{
oldtail = _tail.load();//用load取出当前\_tail节点的值
}
//如果当前tail节点的下一个节点是空,也就是等于参数一,那么修改为参数二
while (oldtail->_next.compare\_exchange\_weak(nullnode, newnode) != true);
//由于现在的真正的尾节点是newnode,所以将\_tail节点更新为newnode
_tail.compare\_exchange\_weak(oldtail, newnode);
}
这里要明白,while条件里的CAS语句是为了插入这个新节点,而最后一句是为了将控制整个队列的_tail指针修改为指向新尾部
无锁队列如何出队列?
出队列比插入更简单,那么废话不多说,直接看看出队列如何实现:
T DeQueue()
{
Node\* oldhead = _head.load();//取出当前的头节点,也就是哑节点
T ret;
do
{
Node\* next = oldhead->_next;//取出头节点的下一个节点,此节点中有我们想要的值
if(next == nullptr)
{
return T();
}
else
{
ret = next->_value;//取走值
}
}
//将头节点(哑节点)修改为下一个节点
while(_head.compare\_exchange\_weak(oldhead,oldhead->_next) != true);
delete oldhead;
return ret;
}
判断队列是否为空和其他问题
判断是否为空,直接判断头部和尾部是否指向同一个节点
bool Empty()
{
return _head.load() == _tail.load();
}
因为我们的无锁队列是不能够被拷贝的,所以我们使用delete关键字直接将拷贝构造函数删除掉
LockFreeQueue(const LockFreeQueue<T>&) = delete;
到这里,我们的无锁队列就实现完成了,之所以这么简单,还是因为C++库为我们提供了atomic这个类。无锁队列这里重在理解他的如何使用CAS的思想去更新节点的。
无锁队列全部源码和测试
经过笔者多次测试,发现无锁队列总体来说还是不错的,比加锁的队列能快一倍左右(第一行为无锁队列),这是插入10w数据的一次的结果,基本在这两个数字中波动。但是无锁队列对于线程数量的选择很关键。随着线程数增大,最后会比加锁队列更差,原因不难理解,CAS机制本身还是太消耗CPU资源了。
#pragma once
#include<atomic>
#include<iostream>
#include<queue>
#include<thread>
#include<mutex>
#include<condition\_variable>
using namespace std;
template<class T>
class LockFreeQueue
{
struct Node
{
T _value;
std::atomic<Node\*> _next;
Node(const T& x)
: \_value(x)
, \_next(nullptr)
{}
};
public:
LockFreeQueue()
{
_head = _tail = new Node(T());
}
LockFreeQueue(const LockFreeQueue<T>&) = delete;
~LockFreeQueue()
{
Node\* cur = _head;
while (cur)
{
Node\* next = cur->_next;
delete cur;
cur = next;
}
}
void Enqueue(const T& x)
{
Node\* newnode = new Node(x);
Node\* oldtail = nullptr;
Node\* nullnode = nullptr;
do
{
oldtail = _tail.load();
} while (oldtail->_next.compare\_exchange\_weak(nullnode, newnode) != true);
_tail.compare\_exchange\_weak(oldtail, newnode);
}
T Dequeue()
{
Node\* oldhead = _head.load();
T headvalue;
do
{
Node\* next = oldhead->_next;
if (next == nullptr)
{
return T();
}
else
{
headvalue = next->_value;
}
}
while (_head.compare\_exchange\_weak(oldhead, oldhead->_next) != true);
delete oldhead;
return headvalue;
}
bool Empty()
{
return _head.load() == _tail.load();
}
private:
std::atomic<Node\*> _head;
std::atomic<Node\*> _tail;
};
mutex mutx;
int main()
{
LockFreeQueue<int> lq;
queue<int> nq;
atomic<size_t> n = 100000;
//测试无锁队列
{
size_t costtime = 0;
vector<thread> threads;
for (size_t i = 0; i < 3; ++i)
{
threads.push\_back(thread([&]()
{
size_t begin = 0, end = 0;
![img](https://img-blog.csdnimg.cn/img_convert/30d9495c2ed6aebfa6ef7ce3b132a546.png)
![img](https://img-blog.csdnimg.cn/img_convert/0b052cf70a13355a27a56ac2fc160d40.png)
**网上学习资料一大堆,但如果学到的知识不成体系,遇到问题时只是浅尝辄止,不再深入研究,那么很难做到真正的技术提升。**
**[需要这份系统化的资料的朋友,可以添加戳这里获取](https://bbs.csdn.net/topics/618668825)**
**一个人可以走的很快,但一群人才能走的更远!不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!**
**网上学习资料一大堆,但如果学到的知识不成体系,遇到问题时只是浅尝辄止,不再深入研究,那么很难做到真正的技术提升。**
**[需要这份系统化的资料的朋友,可以添加戳这里获取](https://bbs.csdn.net/topics/618668825)**
**一个人可以走的很快,但一群人才能走的更远!不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!**