无锁消息队列的设计实现

无锁队列的需求分析:

多线程访问共享队列的数据时,必须确保对共享队列操作的原子性,有以下情况:

1.生产者,例如tcp服务器接收到请求信息,需要将请求信息push进共享队列

2.消费者,例如线程池的工作线程,需要从共享队列中pop/get一个请求

这两种操作都要求对队列进行修改

确保原子性方式

1.对队列的修改操作加锁(系统调用),

可以确保共享队列的线程安全,但是性能较低,并且可能造成死锁

2.使用原子变量(c++)

使用硬件提供的锁机制,性能较高

3.使用c++的对原子变量的原子操作时需要考虑内存顺序模型,确保原子操作之间的顺序性。

可以看出,实现共享队列的原子性,绕不开使用锁机制

分析多线程锁竞争情况:

1.生产者和生产者之间,多个生产者线程向共享队列push数据,发生锁的竞争

2.消费者和消费者之间,多个消费者线程从队列中pop数据,发生锁的竞争

3.生产者和消费者之间,生产者线程push数据,消费者线程pop数据,发生锁的竞争

由于既定的业务处理逻辑,多生产者之间的锁竞争无法避免,多消费者之间的锁竞争也无法避免

但是生产者和消费者之间的锁竞争是可以通过队列的设计来实现的,接下来就要介绍优雅的队列分离代码

优化锁竞争(生产者和消费者)

我们可以观察到,各个线程发生锁竞争的原因是:共享一个队列

那么使用多个队列是不是就可以避免竞争了?

不是,使用一个共享队列的原因是,需要确保所有线程使用的数据都是一致的,否则会出现同一个任务被执行两次的情况,而同一个队列逻辑严格的保证了数据的一致性,同步性。使用多个队列需要确保所有队列的数据实时一致,这是很难做到的,并且平添了复杂性

方案:所有消费者和所有生产者各使用一个队列

这样还能确保两个队列的数据实时一致吗?

逻辑:

1.生产者将用户请求pop进生产者队列

2.消费者从消费者队列取出请求

3.当消费者判断消费者队列为空,而生产者队列不空时,交换两个队列

这里的交换操作,实际上就是将生产者队列中的数据转移到消费者队列中,这样一来,生产者和消费者使用的数据是完全同步

抽象:使用两个队列的策略实际上相当于消费者从共享队列中一次取出多个请求,保存在自己的队列中多次使用,减少了pop操作。是不是特别像cpu的缓存,从主存中一次取出多条指令或数据,加快处理效率

代码实现:

static size_t __msgqueue_swap(msgqueue_t *queue)
{
	void **get_head = queue->get_head;
	size_t cnt;

	queue->get_head = queue->put_head;
	pthread_mutex_lock(&queue->put_mutex);
	while (queue->msg_cnt == 0 && !queue->nonblock)
		pthread_cond_wait(&queue->get_cond, &queue->put_mutex);

	cnt = queue->msg_cnt;
	if (cnt > queue->msg_max - 1)
		pthread_cond_broadcast(&queue->put_cond);

	queue->put_head = get_head;
	queue->put_tail = get_head;
	queue->msg_cnt = 0;
	pthread_mutex_unlock(&queue->put_mutex);
	return cnt;
}

可以看出“交换队列”是通过改变生产者队列指针消费者队列指针的指向实现的,很巧妙!我在看代码前以为是先copy再删除呢

注意这个交换队列的操作发生在消费者线程中,此时对生产者队列的修改操作会引起生产者和消费者的锁竞争,当然这是队列分离策略的唯一发生此竞争的位置

最核心的减少锁竞争的逻辑已经介绍完,接下来介绍无锁队列的原子操作

1.put(生产者向生产者队列中添加一个节点)

2.get(消费者从消费者队列中取出一个节点)

先介绍队列结构:

struct __msgqueue
{
	size_t msg_max; 
	size_t msg_cnt;
	int linkoff; 
	int nonblock;
	void *head1; // 消费者队列的头指针
	void *head2; // 生产者队列的头指针
	void **get_head; // 消费者队列的头指针
	void **put_head; // 生产者队列的头指针
	void **put_tail; // 生产者队列的尾指针
	pthread_mutex_t get_mutex; // 消费者的互斥锁
	pthread_mutex_t put_mutex; // 生产者的互斥锁
	pthread_cond_t get_cond; // 消费者的条件变量
	pthread_cond_t put_cond; // 生产者的条件变量
};

1.put

void msgqueue_put(void *msg, msgqueue_t *queue)
{
	void **link = (void **)((char *)msg + queue->linkoff);

	*link = NULL;
	pthread_mutex_lock(&queue->put_mutex); // 对临界区加锁(修改队列)
	while (queue->msg_cnt > queue->msg_max - 1 && !queue->nonblock) // 队列已满且非阻塞
		pthread_cond_wait(&queue->put_cond, &queue->put_mutex); // 条件等待
// 被信号唤醒,执行put操作
	*queue->put_tail = link;
	queue->put_tail = link;
	queue->msg_cnt++;
	pthread_mutex_unlock(&queue->put_mutex); // 解锁
	pthread_cond_signal(&queue->get_cond); // 唤醒因为没有任务而阻塞的消费者线程
}

2.get

void *msgqueue_get(msgqueue_t *queue)
{
	void *msg;

	pthread_mutex_lock(&queue->get_mutex); // 上锁保护临界区(修改队列)
	if (*queue->get_head || __msgqueue_swap(queue) > 0) 
        // 如果消费者队列不空,继续执行,如果消费者队列为空,交换队列,如果交换成功(生产者队列不空)则继续执行,否则会阻塞在swap函数!
	{
		msg = (char *)*queue->get_head - queue->linkoff;
		*queue->get_head = *(void **)*queue->get_head;
	}
	else 
		msg = NULL;

	pthread_mutex_unlock(&queue->get_mutex);
	return msg;
}

总结:无锁队列并不是不使用锁的队列,而是使用原子变量和原子操作以及队列优化的策略来提高多线程对共享数据的并发访问的性能

推荐学习 https://xxetb.xetslk.com/s/p5Ibb

  • 22
    点赞
  • 17
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
分布式架构 漫谈分布式架构 初识分布式架构与意义 如何把应用从单机扩展到分布式 大型分布式架构演进过程 分布式架构设计 主流架构模型-SOA架构和微服务架构 领域驱动设计及业务驱动规划 分布式架构的基本理论CAP、BASE以及其应用 什么是分布式架构下的高可用设计 构架高性能的分布式架构 构建分布式架构最重要因素 CDN静态文件访问 分布式存储 分布式搜索引擎 应用发布与监控 应用容灾及机房规划 系统动态扩容 分布式架构策略-分而治之 从简到难,从网络通信探究分布式通信原理 基于消息方式的系统间通信 理解通信协议传输过程中的序列化和反序列化机制 基于框架的RPC通信技术 WebService/ApacheCXF RMI/Spring RMI Hession 传统RPC技术在大型分布式架构下面临的问题 分布式架构下的RPC解决方案 Zookeeper 分布式系统的基石 从0开始搭建3个节点额度zookeeper集群 深入分析Zookeeper在disconf配置中心的应用 基于Zookeeper Watcher 核心机制深入源码分析 Zookeeper集群升级、迁移 基于Zookeeper实现分布式服务器动态上下线感知 深入分析Zookeeper Zab协议及选举机制源码解读 Dubbo 使用Dubbo对单一应用服务化改造 Dubbo管理中心及及监控平台安装部署 Dubbo分布式服务模块划分(领域驱动) 基于Dubbo的分布式系统架构实战 Dubbo负载均衡策略分析 Dubbo服务调试之服务只订阅及服务只注册配置 Dubbo服务接口的设计原则(实战经验) Dubbo设计原理及源码分析 基于Dubbo构建大型分布式电商平台实战雏形 Dubbo容错机制及扩展性分析 分布式解决方案 分布式全局ID生成方案 session跨域共享及企业级单点登录解决方案实战 分布式事务解决方案实战 高并发下的服务降级、限流实战 基于分布式架构下分布式锁的解决方案实战 分布式架构实现分布式定时调度 分布式架构-中间件 分布式消息通信 消息中间件在分布式架构中的应用 ActiveMQ ActiveMQ高可用集群企业及部署方案 ActiveMQ P2P及PUB/SUB模式详解 ActiveMQ消息确认及重发策略 ActiveMQ基于Spring完成分布式消息队列实战 Kafka Kafka基于Zookeeper搭建高可用集群实战 kafka消息处理过程剖析 Java客户端实现Kafka生产者与消费者实例 kafka的副本机制及选举原理剖析 基于kafka实现应用日志实时上报统计分析 RabbitMQ 初步认识RabbitMQ及高可用集群部署 详解RabbitMQ消息分发机制及主题消息分发 RabbitMQ消息路由机制分析 RabbitMQ消息确认机制 Redis redis数据结构分析 Redis主从复制原理及无磁盘复制分析 Redis管道模式详解 Redis缓存与数据库一致性问题解决方案 基于redis实现分布式实战 图解Redis中的AOF和RDB持久化策略的原理 redis读写分离架构实践 redis哨兵架构及数据丢失问题分析 redis Cluster数据分布算法之Hash slot redis使用常见问题及性能优化思路 redis高可用及高并发实战 缓存击穿、缓存雪崩预防策略 Redis批量查询优化 Redis高性能集群之Twemproxy of Redis 数据存储 MongoDB NOSQL简介及MongoDB支持的数据类型分析 MongoDB可视化客户端及JavaApi实践 手写基于MongoDB的ORM框架 MongoDB企业级集解决方案 MongoDB聚合、索引及基本执行命令 MongoDB数据分片、转存及恢复策略 MyCat MySQL主从复制及读写分离实战 MySQL+keepalived实现双主高可用方案实践 MySQL高性能解决方案之分库分表 数据库中间件初始Mycat 基于Mycat实习MySQL数据库读写分离 基于Mycat实战之数据库切分策略剖析 Mycat全局表、Er表、分片预警分析 Nginx 基于OpenResty部署应用层Nginx以及Nginx+lua实战 Nginx反向代理服务器及负载均衡服务器配置实战 利用keepalived+Nginx实战Nginx高可用方案 基于Nginx实现访问控制、连接限制 Nginx动静分离实战 Nginx Location ReWrite 等语法配置及原理分析 Nginx提供https服务 基于Nginx+lua完成访问流量实时上报Kafka的实战 Netty 高性能NIO框架 IO 的基本概念、NIO、AIO、BIO深入分析 NIO的核心设计思想 Netty产生的背景及应用场景分析 基于Netty实现的高性能IM聊天 基于Netty实现Dubbo多协议通信支持 Netty无锁化串行设计及高并发处理机制 手写实现多协议RPC框架
C++无锁队列是一种高效的并发数据结构,它不需要使用锁或其他同步机制来实现线程安全。这意味着多个线程可以同时并发地访问队列,而不会导致死锁或竞争条件等问题。 无锁队列通常使用CAS(Compare-And-Swap)操作实现。CAS操作是一种原子操作,它将内存位置的值与预期值进行比较,如果相等,则将该位置的值更改为新值。由于CAS操作是原子的,因此多个线程可以同时尝试更新同一个位置,但只有一个线程能成功地更新值。 基于CAS操作,我们可以实现一个简单的无锁队列。下面是一个基于链表的无锁队列的示例代码: ```c++ template<typename T> class LockFreeQueue { private: struct Node { T data; Node* next; Node(const T& data) : data(data), next(nullptr) {} }; std::atomic<Node*> head; std::atomic<Node*> tail; public: LockFreeQueue() : head(new Node(T())), tail(head.load()) {} // 入队 void push(const T& data) { Node* node = new Node(data); Node* prevTail = tail.exchange(node); prevTail->next = node; } // 出队 bool pop(T& data) { Node* oldHead = head.load(); Node* newHead = oldHead->next; if (newHead == nullptr) { return false; } data = newHead->data; if (head.compare_exchange_strong(oldHead, newHead)) { delete oldHead; return true; } else { return false; } } }; ``` 在这个无锁队列实现中,我们使用一个链表来存储元素。队列的头部指针和尾部指针都是原子指针类型。在入队操作中,我们首先创建一个新节点,然后使用原子操作将它添加到队列的尾部。在出队操作中,我们首先获取队列的头部节点,并从中提取数据。然后,我们使用compare_exchange_strong操作将头部指针更新为下一个节点,以确保只有一个线程可以出队。如果compare_exchange_strong操作失败,则表示另一个线程已经更新了头部指针,我们需要重试出队操作。 需要注意的是,无锁队列实现可能比锁定队列更复杂。当多个线程同时访问队列时,可能会出现ABA问题或竞态条件等问题。因此,设计无锁队列需要仔细考虑所有可能的情况,并确保线程安全性和正确性。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

wjq++

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值