Linux无锁共享内存,优秀数据结构学习 - 共享内存无锁队列的实现(二)
优秀数据结构学习 - 共享内存无锁队列的实现(二)
优秀数据结构学习 - 共享内存无锁队列的实现(二)
1 关键技术
操作系统提供的进程间通信机制有文件、socket、消息队列、管道、共享内存等。其中,共享内存是最快的IPC机制[6]。
共享内存映射到进程空间后,数据可以直接从共享内存进行读写,不需要执行系统调用进行数据的传输。因此,避免了其它进程间通信机制必须的用户态/内核态切换以及用户空间与内核空间的数据拷贝。
由于消息队不需支持跨主机通信,所以可以采用共享内存进行进程间的通信。
消息队列需要支持多个写者,在多个写者同时进行写操作时,会产生并发操作。处理并发操作,通常的解决方案是使用互斥锁。使用互斥锁简单方便,但有一定的系统开销。在我们的测试环境下,单次锁/解锁操作耗时大概为20纳秒。多个线程互斥操作时,时间开销大大增加。同样的测试条件,当两个线程并发操作时,单次锁/解锁操作的平均时延为320纳秒。
如果并发操作能限制在一个机器字节内,可以使用CPU提供的CAS指令,即’Compare& Swap’原子操作,进行进程间的互斥操作。
相较于使用互斥锁进行多进程间的互斥操作,使用CAS指令的程序“临界区”更小,只有一个字节。当多个写者同时更新“临界区”时,只有一个写者成功,其它写者需要重复操作并检查结果。这样避免了进程锁起时休眠,以及锁操作带来的开销。最大程度的降低响应时延。
无锁程序的关键在于程序设计时,将程序的”临界区”设置为一个机器字节的变量,进而可以使用CAS指令进行原子操作。
1.2.1 消息队列多写者的无锁操作
在消息队列中,数组和数组的头尾head、tail指针是多进程访问的临界区。
只考虑写者,操作临界区为向数组写入数据、tail指针的更新:
critical section begin
write to A[tail]
tail++
critical section end
减少临界区内容,在临界区只对tail指针进行修改:写者竞争获取到tail针后,再写入数据:
critical section begin
s = tail
tail++
critical section end
write to A[s]
现在,临界区只有tail一个变量,使用CAS指令代替互斥锁:
s = tail
do{
next tail = s+1;
ret = CAS(&tail, s, next_tail);
if(s == ret)
break;
s = ret;
}while(1);
write to A[s];
1.2.2 队列读操作
由于只有一个读者,因此不需考虑head指针并发操作的问题。但由于写者先更新tail指针内容,后写入数据,读者在读数据前需要先判断数据是否有效。
消息队列的消息格式如下:
struct {
volatile unsigned int data_len;
char data[0];
};
写者写入数据前,data_len为0,写入数据后,更新data_len内容。此时,读者才开始读数据。读者读取数据后,重置data_len为0;这样,下次写者写入该位置数据完成前,读者不会提前读取。
2 消息队列设计
消息队列不是作为独立的服务,而是设计成库的形式提供使用。应用程序调用消息队列库的接口,编译时将库文件链接到目标文件即可。
按照功能划分,共享内存消息队列划分为消息队列、消息队列创建、消息队列销毁、数据读取、数据写入、消息队列查询等模块,各模块对应的功能如下:
消息队列: 提供基础数据结构,缓存写入的数据,供其他模块读取或者查询。
消息队列创建: 创建指定键值、大小和容量的共享内存消息队列。
消息队列销毁: 销毁指定键值的共享内存消息队列,释放共享内存。
数据读取: 读取共享内存消息队列中的数据。
数据写入: 向共享内存消息队列中写入数据。
消息队列查询: 查询共享内存消息队列的使用情况。
2.1.1 总体组成
共享内存消息队列的组成如下图,其中消息队列由消息队列创建模块创建,供数据读写模块、消息队列查询模块使用,最终被消息队列销毁模块销毁释放。
2.1.2 消息队列组成
消息队列是一个基于共享内存的环形队列,在开辟的一块连续的共享内存中,保存消息队列相关信息,缓存写入的数据。它在内存中的结构定义:
消息队列头包含队列大小、消息记录大小,head、tail位置指针,用于读写同步的条件变量和锁等控制信息。环形队列分别使用首指针head和尾指针tail标记队列的头和尾,读者从head指向的位置读取数据,写者向tail指向的位置写入数据。
2.2.1 创建和销毁
消息队列应该在应用进程启动前创建,在应用进程退出后再销毁。创建和销毁由独立的程序调用创建和销毁模块进行操作。
创建消息队列过程主要包括:
1. 根据参数确定消息长度、个数,计算队列缓存大小
2. 申请共享内存
3. 初始化消息队列头部控制信息,初始化队列缓存
销毁消息队列过程主要包括:
1. 销毁消息队列头部控制信息
2. 释放共享内存其中控制信息主要包括读者、写者同步需要的条件变量和互斥锁。
2.2.2 数据写入
消息队列支持多个写者同时写操作,多个写者对队列的竞争写由CAS操作完成。写操作的过程如下:
1. 检查环形队列空闲数量是否大于临界值
2. 如果是,执行3;如果否,超时等待,继续执行1
3. 获取当前tail值,new_tail为tail+1
4. 通过CAS指令,使用new_tail更新tail
5. 如果成功,根据new_tail位置写入数据,然后更新该位置消息的data_len为实际数据长度;如果不成功,跳到1继续执行
6. 如果有读者在等待,通知读者
值得注意的是,写者先更新tail指针,后写入数据。这样才能保证多个写者通过CAS进行互斥操作。但是tail指针更新后,不保证数据已经更新完成。读者在读数据时需要根据data_len的值判断数据是否完整。
2.2.3 数据读取
消息队列只有一个读者,逻辑比较简单,读操作过程如下:
1. 检查环形队列是否有数据
2. 如果是,head自加1;如果否,超时等待,执行1
3. 检查环形队列head位置的数据data_len是否为0
4. 如果是,超时等待,若超过500毫秒data_len仍为0,认为写者异常,跳过该位置,执行1;如果否,读取数据
5. 更新该位置data_len为0
6. 如果有写者在等待,通知写者
2.2.4 读写同步
读者和写者使用条件变量进行同步。条件变量保存在消息队列头部信息中。只有当写者因没有空闲位置进入等待状态时,读者读取数据后才会发送条件变量信号进行通知。同样,只有当读者因没有数据而进入等待状态时,写者写入数据后才发送条件变量信号进行通知。
详细介绍:https://mp.weixin.qq.com/s/RqHsX3NIZ4_BS8O30KWYhQ
优秀数据结构学习 - 共享内存无锁队列的实现(二)相关教程
达梦数据库学习之ODBC
达梦数据库学习之ODBC 这里的安装包都是在网上自行寻找。 再这里分享一下链接:https://pan.baidu.com/s/11eWq2lRmnfHvERPD1xTqOA 提取码:gf0w 一、yum安装 1、YUM 安装UnixODBC库: yum install -y unixODBC.x86_64 unixODBC-devel.x86_64 2、vim /etc/odb
数据结构32:选择排序
数据结构32:选择排序 选择排序Selection Sort 选择排序对冒泡排序进行了改进,保留了其基本的多趟对比的思路,每趟都使当前最大项就位。 但是选择排序对交换进行了削弱,每趟仅进行一次交换,记录最大项的所在位置,最后再跟本趟最后一项进行交换。 选择排序
【亡羊补牢】挑战数据结构与算法 第48期 LeetCode 104. 二叉树的
【亡羊补牢】挑战数据结构与算法 第48期 LeetCode 104. 二叉树的最大深度(二叉树) 仰望星空的人,不应该被嘲笑 给定一个二叉树,找出其最大深度。 二叉树的深度为根节点到最远叶子节点的最长路径上的节点数。 说明: 叶子节点是指没有子节点的节点。 示例:
数据结构31:冒泡排序
数据结构31:冒泡排序 目录 一、冒泡排序Bubble Sort 二、算法分析 三、性能改进 一、冒泡排序Bubble Sort 冒泡排序的算法思路在于对无序表进行多趟比较交换,每趟包括了多次两两相邻比较,并将逆序的数据项交换位置,最终能将本趟的最大项就位。经过n-1趟比
数据结构(10)_递归
数据结构(10)_递归 1.递归的思想 递归时一种数学上分而自治的思想,将原有问题分解为规模较小的问题进行处理。 1.分解后的问题与原问题的类型完全相同,但规模较小; 2.通过小规模问题的解,能够轻易的求得原问题的解。 问题的分解是有限的(递归不能无限进行
数据结构(09)_字符串类的实现
数据结构(09)_字符串类的实现 1.字符串类的创建(上) C语言不支持真正意义上的字符串,使用字符指针和字符数组实现字符串存储,使用C库函数实现字符串操作。 C++为了兼容C语言,也不支持原生的字符串类型,但可以通过自定义类类型完成字符串类型的定义。 继承
数据结构(06)_栈
数据结构(06)_栈 1.栈的设计和实现 概念: 栈是一种特殊的线性表,仅能在线性表的一端(栈顶)进行操作。 栈的特性: 后进先出(last in first out) 栈的基本操作: 创建栈(stack()); 销毁栈(~stack()); 清空栈(clear()) 进栈(push()); 出栈(pop()
数据结构(07)_队列
数据结构(07)_队列 1. .队列的概念和实现 队列是一种特殊的线性表,仅能在线性表的两端进行操作。 -队头(front)取出数据元素的一端; -队尾(rear)插入数据元素的一端。 队列的特性: 队列的常用操作: 创建和销毁;出队和入队;清空队列;获取队首元素;
Linux无锁共享内存,优秀数据结构学习 - 共享内存无锁队列的实现(二)相关推荐
- ajax无刷新评论的思路,ajax学习——ajax版无刷新评论(数据库)
//Comment.htm 无刷新评论 type="text/javascript"> //加载评论 $(function() { $.post("GetComme ...
- linux学习---基于内存的IPC(共享内存,信号量数组,消息队列)
常用的IPC分为两个类别,一是基于文件,而是基于内存 基于文件的分别有匿名管道,有名管道,普通的文件共享,socket文件 如果要看基于文件的IPC,请参考:http://blog.csdn.net/ ...
- Linux进程通信的四种方式——共享内存、信号量、无名管道、消息队列|实验、代码、分析、总结
Linux进程通信的四种方式--共享内存.信号量.无名管道.消息队列|实验.代码.分析.总结 每个进程各自有不同的用户地址空间,任何一个进程的全局变量在另一个进程中都看不到,所以进程之间要交换数据必须 ...
- Linux访问其他进程空间,Linux环境进程间通信系列(五):共享内存
共享内存可以说是最有用的进程间通信方式,也是最快的 IPC 形式.两个不同进程 A . B 共享内存的意思是,同一块物理内存被映射到进程 A . B 各自的进程地址空间.进程 A 可以即时看到进程 B ...
- Linux环境进程间通信系列(五):共享内存
共享内存(上) 共享内存可以说是最有用的进程间通信方式,也是最快的IPC形式.两个不同进程A.B共享内存的意思是,同一块物理内存被映射到进程A.B各自的进程地址空间.进程A可以即时看到进程B对共享内存 ...
- Linux 3.进程间通信(shmget shmat shmdt shmctl 共享内存、signal signaction sigqueue 信号、semget semctl semop 信号量)
Linux 3.进程间通信(IPC) 共享内存 共享内存的接口指令 shmget 创建获取获取共享内存 shmat 映射:连接共享内存到当前进程的地址空间 shmdt 断开与共享内存的连接 shmct ...
- 《深入理解LINUX内存管理》学习笔记(一)
引子 为什么要写这个笔记: 1,这本书的中文版翻译了太垃圾,没法阅读.阅读英文原版,可以很好的理解作者的思路.作此笔记备忘 2,一直以来学习LINUX kernel的知识缺乏系统化,借对这本书的学习, ...
- VoltDB介绍——本质:数据保存在内存,充分利用CPU,单线程去锁,底层数据结构未知...
转自:http://blog.csdn.net/ransom0512/article/details/50440316 简介 VoltDB数据库是一个分布式,可扩展,shared-nothing的内存 ...
- Linux 进程间通讯(IPC)方式 ------- 共享内存
Linux 进程间通讯(IPC)方式有以下几种: 1->管道(pipe)和有名管道(fifo). 2->消息队列 3->共享内存 4->信号量 5->信号(signal) ...
最新文章
- 2019.01.07|区块链技术头条
- Mysql中的增删改查操作
- BZOJ 4720: [Noip2016]换教室
- 德勤预判:2022技术七大趋势
- 10.傅里叶变换——傅里叶变换、计算傅里叶变换_3
- 深度学习2.0-普通BP神经网络
- Windows10 安装 protobuf
- 命令行快速访问远程目录
- verifycode.php,verifycode.php
- 自己写的一个简单JAVA网络通讯录
- 网络编程在线英英词典之历史查询模块(六)
- java 时间轮_惊艳的时间轮定时器
- windows共享时出现“指定网络名不再可用”解决办法
- Return value (126) was not iterable.
- 2、树莓派声卡设置和alsactl命令的使用
- tools: USB、MiniUSB、MicroUSB接线
- vue 文字无缝滚动_vue文字横向滚动公告
- macOS Big Sur 11.6 (20G165) 虚拟机 IOS 镜像
- 利用软路由,轻松实现宽带叠加,已达到千兆网速的效果
- 安装mysql数据库