基于滑动窗口的免锁队列设计实现

消息队列是一些平台的通信的基石,各个任务的通信基于消息队列,消息队列的处理速度往往影响整个系统的性能,为了避免多任务同时处理消息队列,通常有任务处理队列时需要加锁来互斥访问。

1:假设每个模块有自己的消息队列,任何模块都可以给这个模块发消息,但只有本模块会从消息队列中取消息处理,如下图所示一个消息队列可能多个任务同时写,一个任务读。

2:为了避免多个任务处理时使用锁导致的效率底线我们可以使用免锁设计来实现队列操作,见http://www.cnblogs.com/chencheng/p/3527692.html

3:上面的设计在多个任务入队操作效率很高,但在的问题是如果队列中存在元素可读,但队头元素始终没有准备好,Tout任务会一直轮询而不能处理后面已经准备好的元素。

4:为了提高任务消息处理效率,可以设计一种基于滑动窗口机制的消息队列来处理出队操作,避免轮询。

1)任务处理队头元素,如果队头元素可读,马上读出队头元素操作:

2)任务处理队头元素,如果队头元素不可读,但队列中存在可读的消息,设置处理窗口为当前队尾到队头的空间,顺序的在窗口中取出可读数据进行处理,直到该窗口所有数据处理完毕:

3)当前窗口处理完毕后,移动队头位置,构建下一个滑动窗口处理数据。

5:通过上述设计可以提高消息处理的速度,避免轮询等待。

6:后记,问题定位

  • 想通过自身比较来取模,结果发现偶尔CAS(&(g_que.rear),g_que.rear,g_que.rear%MAXLEN) &(g_que.rear)地址中的值并不等于g_que.rear。

1:gcc -lpthread -g -o FreeLockQueue FreeLockQueue.c 用-g选项编译

2:objdump -S FreeLockQueue 反汇编

3:    CAS(&(g_que.rear),MAXLEN,0);
  40079f: b8 d0 07 00 00        mov    $0x7d0,%eax
  4007a4: ba 00 00 00 00        mov    $0x0,%edx
  4007a9: f0 0f b1 15 53 d4 20  lock cmpxchg %edx,0x20d453(%rip)        # 60dc04 <g_que+0xbb84>

4:    CAS(&(g_que.rear),g_que.rear,g_que.rear%MAXLEN);
  40079f: 8b 0d 5f d4 20 00     mov    0x20d45f(%rip),%ecx        # 60dc04 <g_que+0xbb84>
  4007a5: ba d3 4d 62 10        mov    $0x10624dd3,%edx
  4007aa: 89 c8                 mov    %ecx,%eax
  4007ac: f7 ea                 imul   %edx
  4007ae: c1 fa 07              sar    $0x7,%edx
  4007b1: 89 c8                 mov    %ecx,%eax
  4007b3: c1 f8 1f              sar    $0x1f,%eax
  4007b6: 29 c2                 sub    %eax,%edx
  4007b8: 89 d0                 mov    %edx,%eax
  4007ba: 69 c0 d0 07 00 00     imul   $0x7d0,%eax,%eax
  4007c0: 29 c1                 sub    %eax,%ecx
  4007c2: 89 c8                 mov    %ecx,%eax
  4007c4: 89 c2                 mov    %eax,%edx
  4007c6: 8b 05 38 d4 20 00     mov    0x20d438(%rip),%eax        # 60dc04 <g_que+0xbb84>
  4007cc: f0 0f b1 15 30 d4 20  lock cmpxchg %edx,0x20d430(%rip)        # 60dc04 <g_que+0xbb84>

5:CAS(&(g_que.rear),g_que.rear,g_que.rear%MAXLEN)的参数计算从汇编看是多条指令,并不具有原子性,原子操作 是比较和交换本身,如果将g_que.rear参数的值移入寄存器后,发生线程切换,另外一个线程修改了g_que.rear的值, 那么切换回来后&(g_que.rear)地址中的值和寄存器中保存的值并不相同。

结论:原子操作仅仅是针对操作本身,小心参数求解可能发生的切换。

  • g_que.readableCnt--;汇编指令如下,

400bd4: 8b 05 32 d0 20 00     mov    0x20d032(%rip),%eax        # 60dc0c <g_que+0xbb8c>
  400bda: 83 e8 01              sub    $0x1,%eax
  400bdd: 89 05 29 d0 20 00     mov    %eax,0x20d029(%rip)        # 60dc0c <g_que+0xbb8c>

1:先把地址中的值取到寄存器,再将寄存器中的值减一,最后将寄存器的值存入地址,可以看到如果在第一条指令执行后被切换出去,其它线程如果操作了readableCnt再切换回来,此刻值就出问题
2:g_que.readableCnt初始为1,执行g_que.readableCnt--
3:执行指令1后被切换出去,此刻eax中为1,
4:另一个线程操作g_que.readableCn+1,完后被切换回来
5:执行指令2,eax中值为0
6:执行指令3,将0存回g_que.readableCn,最后g_que.readableCn为0.
7:与我们预期的值1不一致了。所以应该使用FAS(&(g_que.readableCnt),1)原子操作保证一致性。

转载请注明原始出处:http://www.cnblogs.com/chencheng/p/3961263.html

时间: 2024-10-01 00:20:43

基于滑动窗口的免锁队列设计实现的相关文章

基于循环数组的无锁队列

在之前的两篇博客(线程安全的无锁RingBuffer的实现,多个写线程一个读线程的无锁队列实现)中,分别写了在只有一个读线程.一个写线程的情况下,以及只有一个写线程.两个读线程的情况下,不采用加锁技术,甚至原子运算的循环队列的实现.但是,在其他的情况下,我们也需要尽可能高效的线程安全的队列的实现.本文实现了一种基于循环数组和原子运算的无锁队列.采用原子运算(compare and swap)而不是加锁同步,可以很大的提高运行效率.之所以用循环数组,是因为这样在使用过程中不需要反复开辟内存空间,可

【deque】滑动窗口、双端队列解决数组问题

C++手册之deque 所属头文件 <deque> 常用操作: back()返回尾部元素: front()返回头部元素: push_back()尾部插入元素: pop_bakc()尾部删除元素: push_front()头部插入元素: pop_front()头部删除元素: 问题1:求滑动窗口的最大值(<剑指offer面试题65>) 描述:给定一个数组和滑动窗口的大小,找出所有滑动窗口里的最大值. 示例:数组{2, 3, 4, 2, 6, 2, 5} 窗口大小为 3,一共有7-3+1

剑指offer:滑动窗口的最大值(栈和队列)

1. 题目描述 /* 给定一个数组和滑动窗口的大小,找出所有滑动窗口里数值的最大值. 例如,如果输入数组{2,3,4,2,6,2,5,1}及滑动窗口的大小3,那么一共存在6个滑动窗口,他们的最大值分别为{4,4,6,6,6,5}: 针对数组{2,3,4,2,6,2,5,1}的滑动窗口有以下6个: {[2,3,4],2,6,2,5,1}, {2,[3,4,2],6,2,5,1}, {2,3,[4,2,6],2,5,1}, {2,3,4,[2,6,2],5,1}, {2,3,4,2,[6,2,5],

滑动窗口的最大值问题

给出一个序列,要求找出滑动窗口中的最大值,比如: # 序列: 2, 6, 1, 5, 3, 9, 7, 4 # 窗口大小: 4 [2, 6, 1, 5], 3, 9, 7, 4 => 6 2, [6, 1, 5, 3], 9, 7, 4 => 6 2, 6, [1, 5, 3, 9], 7, 4 => 9 2, 6, 1, [5, 3, 9, 7], 4 => 9 2, 6, 1, 5, [3, 9, 7, 4] => 9 # 期望结果: [6, 6, 9, 9, 9] 并要

基于redis的延迟消息队列设计

需求背景 用户下订单成功之后隔20分钟给用户发送上门服务通知短信 订单完成一个小时之后通知用户对上门服务进行评价 业务执行失败之后隔10分钟重试一次 类似的场景比较多 简单的处理方式就是使用定时任务 假如数据比较多的时候 有的数据可能延迟比较严重,而且越来越多的定时业务导致任务调度很繁琐不好管理. 队列设计 目前可以考虑使用rabbitmq来满足需求 但是不打算使用,因为目前太多的业务使用了另外的MQ中间件. 开发前需要考虑的问题? 及时性 消费端能按时收到 同一时间消息的消费权重 可靠性 消息

多线程安全的滑动窗口设计实现

滑动窗口是日志模块重要的数据结构,用于日志发送接收以及日志索引查询,和组内同学讨论了的多线程安全的滑动窗口设计,有三种实现方案,写此文档记录下. 1.接口描述 滑动窗口内部使用数组,每个数组项的是一个结构体: Structentry { Struct ValueNode *head; Struct ValueNode *tail; Int64_t cnt; Int64_tstat; } 由于在对同一项多次写入不同值的情况下,写入的多个值会以链表组织,head指向链表头,tail指向链表尾,cnt

详解--单调队列 经典滑动窗口问题

单调队列,即单调的队列.使用频率不高,但在有些程序中会有非同寻常的作用. 动态规划·单调队列的理解 做动态规划时常常会见到形如这样的转移方程: f[x] = max or min{g(k) | b[x] <= k < x} + w[x] (其中b[x]随x单调不降,即b[1]<=b[2]<=b[3]<=...<=b[n]) (g[k]表示一个和k或f[k]有关的函数,w[x]表示一个和x有关的函数) 这个方程怎样求解呢?我们注意到这样一个性质:如果存在两个数j, k,使

滑动窗口的单调队列

今天的训练赛HDU 4122,卡到最后也没出来,结束后和队友冷静分析代码后才发现错在一个简单的错误上,修改后A了 赛后看题解,大家的题解中大都提到了要用单调队列. 去网上搜单调队列..文章无外乎就两种..= =  抄袭好严重呀 1.http://zhonghuan.info/2014/09/16/%E6%B5%85%E6%9E%90%E5%8D%95%E8%B0%83%E9%98%9F%E5%88%97/ 简单的单调队列 2.http://blog.pureisle.net/archives/4

[计算机图形学] 基于C#窗口的Bresenham直线扫描算法、种子填充法、扫描线填充法模拟软件设计(一)

一.首先说明: 这是啥? —— 这是利用C#FORM写的一个用来演示计算机图形学中 ①Bresenham直线扫描算法(即:连点成线):②种子填充法(即:填充多边形):③扫描线填充法 有啥用? ——  无论是连点成线还是区域填充在高级编程中基本上都提供很高效的库函数来调用.这里拿出这些算法一方面有利于大家理解那些封装的函数底层是实现:另一方面是方便嵌入式TFT屏幕底层驱动开发时借鉴的. 是啥样? ——  如下面的操作,不言而喻. 二.进入正题: 2-1.直线的扫描转换 图形的扫描转换实质就是在光栅