完善博文 共享内存一写多读无锁实现的代码逻辑部分

本文聚焦于使用共享内存实现高效的PubSub发布订阅模式以达成进程间通信。介绍了多种通信方式,重点阐述共享内存方式。分析了需求,包括消息传递、管理机制等,还对逻辑进行分析,提出不同方案并比较,最终得出无锁队列的实现思路。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

使用共享内存(内存映射)实现发布订阅模式

  • 多进程实现PubSub发布订阅模式,从而实现进程间的通信。
  • 通信方式可以是TCP/UDP,管道Pipe/消息队列,共享内存shared memory等等。其中TCP/UDP的方式是可以用作局域网以及跨平台的通信,Pipe/消息队列是进程间基于系统实现比较基础的通信,这两者有大量优秀的第三方库支持,如ZeroMQ,只要加入我们自定义数据的转换方式即可方便实现;而共享内存是实现进程间通信最快的方式,但因为共享内存的设计并不是用来做类似PubSub这种模式的实现的,并且共享内存实质上就是一段进程间共享的内存空间,使用自由度是极高的,所以也很少有第三方库来实现共享内存方式的进程间通信。
  • 因此本文的重点是如何使用共享内存shared memory来实现高效的PubSub发布订阅模式。

需求

  • 消息通过事先分配好的共享内存空间来传递
  • 需要有一定的机制来管理消息的发送(写)和接收(读)
  • 需要实现发布订阅模式,也就是一个发布者(一写)多个订阅者(多读)
  • 考虑到平台的原因,最后采用了文件映射内存的这种方式,在各种系统中都有比较通用的实现

逻辑分析

  • 显然,只要创建了一个文件并且设置好需要的大小,即可以使用mmap映射到进程的内存空间,并且在退出时可以用munmap将映射释放掉。但是空间真正的释放是要把文件删掉的,因此我们需要一个计数器来记录使用这块共享内存的进程数,类似共享指针shared_ptr的实现,在计数为零时把文件删掉。在修改这个计数的时候还需要一把进程间读写锁:
  • 对于只有单个订阅者,数据之后包含一个标志位,发布者写完后置为true,订阅者读完之后置为false,可能再加上一个信号灯的控制,来避免频繁读写;
  • 对于多个订阅者,数据中的这个标志位变成一个计数,发布者写完之后将计数器置为订阅者的数量,订阅者读完之后将计数器减1,再加上一个进程条件变量的控制,来避免频繁读写。
  • 这两种方案都有一定的弊端,最大的问题在于,订阅者还需要修改共享内存的内容,这样就发挥不出读写锁支持多读的优势了。我们需要一个更好的机制。
  • 一个简单的实现是数据中带有一个单调递增的标签,订阅者读到数据后本地保存一下这个标签的值,如果下次读到的这个值不比保存的值大,就认为读到了旧数据,忽略之。这个标签比较好的实现是用当前的系统时间而不是计数,因为发布者可能会重启清零,就算重启后可以从已经写入的数据中读取,但后面为了实现无锁队列会让这个事情变得麻烦。这样还有一个问题是,依然会频繁地去读取这个标签。因此需要加入进程条件变量的控制来减少这种频繁。接下来是2,实现消息发送(写)和接收(读)的管理。因为我们已经有了一把读写锁,很自然地想到可以用它来管理读写啊。事实上并不是这样,因为发布者写完数据之后可能会有一段时间不会占有写锁,这时候就要一种机制来限制订阅者不会重复来读这个数据。对于这个实现,已有的方案有:
  • 对于每一个订阅者都开辟一块共享内存,可以按一对一的方式同时复制多份数据;
  • 使用生产消费模式,使用循环队列来实现读写分离。
  • 第1种方案是解决了读写锁争抢的问题,但是增加了内存复制的开销,反而没有第2种方案好。但是我们要稍微修改一下传统的生产消费模式的实现,只用一个指针来指向最新的数据。之所以这样做是因为内存是事先分配好的,我们把它改造成环形的内存缓冲区,很难保证数据读取的序列性;再者就是循环的尾指针应该由订阅者自己来维护,因为每个订阅者处理的速度是不一样的。
  • 如此一来,所有数据的修改完全是由发布者来做的,也就是说对于订阅者来说,这是个无锁队列:

代码实现

#include <iostream>
#include <cstring>
#include <vector>
#include <functional>
#include <memory>
#include <sys/mman.h>
#in
评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值