【并发编程十六】无锁数据结构(2)——无锁队列
上一节我们讲解了无锁栈容器,这节我们讲解下无锁队列,如果大家还有基础知识不了解,建议先看下我的上两篇原子操作和无锁栈容器两篇文章。
一、简介
- 栈容器是后入先出,队列是先入先出。
- 队列和栈容器稍微有点不同,因为对于队列结构,push和pop分别访问其不同的部分;而在栈容器上,这两项操作都访问头节点,所以两种数据结构所需的同步操作相异。
- 如果某线程在队列的一端改动,而另一线程同时访问队列的另一端,代码就要保证前者的改动过程能正确的为后者所见。
二、原理图
无锁队列的实现方式,网上也是五花八门,挺多的。在此,我们讲解下:仅服务单一生产者和单一消费者的无锁队列的实现原理图和对应的代码实现。
- 无锁队列
- 无锁栈容器
三、代码:实现
template<typename T>
class lock_free_queue
{
private:
struct node
{
shared_ptr<T> value;
node* next;
node():value(make_shared<T>()),//根据类型T重新分配一块内存
next(nullptr){}
};
private:
std::atomic<node*> m_head;
std::atomic<node*> m_tail;
private:
node* pop_head()
{
node* const old_head = m_head.load();
if (old_head == m_tail.load())
{
return nullptr;
}
m_head.store(old_head->next);
return old_head;
}
public:
lock_free_queue() :m_head(new node),m_tail(m_head.load())
{
};
lock_free_queue(const lock_free_queue& other) = delete;
lock_free_queue& operator = (const lock_free_queue& other) = delete;
~lock_free_queue()
{
while (node* const old_head = m_head.load())
{
m_head.store(old_head->next);
delete old_head;
}
}
public:
void push(T const& value)
{
//m_tail.store(nullptr);
shared_ptr<T> new_value = make_shared<T>(value);
node* p = new node;
node* const old_tail = m_tail.load();
old_tail->value.swap(new_value);
old_tail->next = p;
m_tail.store(p);
}
shared_ptr<T> pop()
{
node* old_head = pop_head();
if (!old_head)
{
return shared_ptr<T>();
}
shared_ptr<T> const res(old_head->value);
delete old_head;
return res;
}
};
三、代码:demo
task0:生产者线程
task2:消费者线程
- demo
#include <iostream>
#include <atomic>
#include<thread>
#include<string>
using namespace std;
template<typename T>
class lock_free_queue
{
private:
struct node
{
shared_ptr<T> value;
node* next;
node():value(make_shared<T>()),//根据类型T重新分配一块内存
next(nullptr){}
};
private:
std::atomic<node*> m_head;
std::atomic<node*> m_tail;
private:
node* pop_head()
{
node* const old_head = m_head.load();
if (old_head == m_tail.load())
{
return nullptr;
}
m_head.store(old_head->next);
return old_head;
}
public:
lock_free_queue() :m_head(new node),m_tail(m_head.load())
{
};
lock_free_queue(const lock_free_queue& other) = delete;
lock_free_queue& operator = (const lock_free_queue& other) = delete;
~lock_free_queue()
{
while (node* const old_head = m_head.load())
{
m_head.store(old_head->next);
delete old_head;
}
}
public:
void push(T const& value)
{
//m_tail.store(nullptr);
shared_ptr<T> new_value = make_shared<T>(value);
node* p = new node;
node* const old_tail = m_tail.load();
old_tail->value.swap(new_value);
old_tail->next = p;
m_tail.store(p);
}
shared_ptr<T> pop()
{
node* old_head = pop_head();
if (!old_head)
{
return shared_ptr<T>();
}
shared_ptr<T> const res(old_head->value);
delete old_head;
return res;
}
};
lock_free_queue<string> free_queue;
void task0()
{
for (int i = 0; i < 5; i++)
{
cout << "task0\n";
string temp = " i love china_" + to_string(i) + "\n";
free_queue.push(temp);
}
}
void task2()
{
//cout << "task2:";
while (1)
{
shared_ptr<string> temp = free_queue.pop();
string *a = temp.get();
if (nullptr != a)
{
if (0 != a->size())
{
cout << "task2:" << *a << endl;
}
}
}
}
int main()
{
thread t0(task0);
thread t2(task2);
t0.join();
t2.join();
}
- 输出
四、其他知识点
demo中有些知识点,在我们之前的博客中没有说过,我们在此做下简单的说明
1、delete 关键字
- 禁止编译器默认生成的函数
不想使用其中某个,可以将其定义为private,或者使用=delete。 - 只需在想要 “禁止使用” 的函数声明后加 “= delete”,需要保留的则加 “= default” 或者不采取操作
class tree {
public:
tree (){};
~tree (){};
// tree () = default;
// ~tree () = default;
public:
//tree () = delete; //禁止生成默认构造函数
tree (const tree &) = delete; //禁止生成默认拷贝构造函数
//tree (const tree& other) = delete; //禁止生成默认拷贝构造函数
tree (tree &&) = delete; //禁止生成默认移动构造函数
tree &operator=(const tree &) = delete; //禁止生成默认赋值函数
//ree &operator=(const tree & other) = delete; //禁止生成默认赋值函数
tree &operator=(tree &&) = delete; //禁止生成移动拷贝函数
};
2、delete 的扩展
C++11 中,delete 关键字可用于任何函数,不仅仅局限于类成员函数
- 函数重载
在函数重载中,可用 delete 来滤掉一些函数的形参类型,如下:
bool isLucky(int number); // original function
bool isLucky(char) = delete; // reject chars
bool isLucky(bool) = delete; // reject bools
bool isLucky(double) = delete; // reject doubles and floats
这样在调用 isLucky 函数时,如果参数类型不对,则会出现错误提示
if (isLucky('a')) … // error ! call to deleted function
if (isLucky(true)) … // error !
if (isLucky(3.5)) … // error !
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 阿里最新开源QwQ-32B,效果媲美deepseek-r1满血版,部署成本又又又降低了!
· 单线程的Redis速度为什么快?
· SQL Server 2025 AI相关能力初探
· AI编程工具终极对决:字节Trae VS Cursor,谁才是开发者新宠?
· 展开说说关于C#中ORM框架的用法!
2014-03-16 source code analyzer 功能强大的C/C++源代码分析软件 Celerity CRACK 破解版
2014-03-16 分析函数调用关系图(call graph)的几种方法
2014-03-16 用CodeViz绘制函数调用关系图(call graph)
2014-03-16 C++的辅助工具介绍
2013-03-16 位运算中的异或运算 .
2013-03-16 按位与、或、异或等运算方法
2012-03-16 Javascript 对象用法