Q:CAS的实现

A:gcc提供了两个函数

  1. bool __sync_bool_compare_and_swap (type *ptr, type oldval, type newval, ...)
  2.  
  3. //type 的类型有限制 只能是 1,2,4,8字节的整形 或者是指针类型
  4. type __sync_val_compare_and_swap (type *ptr, type oldval, type newval, ...)

这两个函数提供原子的比较和交换,如果*ptr == oldval,就将newval写入*ptr,
第一个函数在相等并写入的情况下返回true,这个函数比第二个好在,返回bool值可以知道有没有更新成功.
第二个函数在返回操作之前的值。

第二个函数可以用c语言描述成:

  1. type __sync_val_compare_and_swap (type *ptr, type oldval, type newval, ...)
  2.  
  3. {
  4.  
  5. type cur = *ptr;
  6.  
  7. if (cur == oldval)
  8.  
  9. {
  10.  
  11. *ptr = newval;
  12.  
  13. }
  14.  
  15. return cur;// 返回操作之前的值
  16.  
  17. }

官方文档里有一段:GCC will allow any integral scalar or pointer type that is 1, 2, 4 or 8 bytes in length.

意思是GCC允许任何长度为1, 2, 4或8字节的整型和指针

不然就会有下面这种错误

Q:简单介绍一下无锁队列

A:

之所以有无锁队列,是因为在多线程环境下有以下情景,

对同一链队列进行入队操作时 一号线程正在将 新的队列节点A 挂载到 队尾节点的next上 可是还没来的及更新队尾节点 但同一时刻二号线程也在进行入队操作 将 新的队列节点B 也挂载到了 没更新的队尾节点的next上 那么一号线程挂载的节点A就丢失了

为了解决多线程环境下的这类问题

我们第一时间肯定想到了加上互斥锁 控制同一时刻只能有一个线程可以对队列进行写操作
但是加锁的操作太消耗系统资源了 很繁重
因为对临界区的操作只有一步 就是对队列的尾节点进行更新
只要让这一步进行的是原子操作就可以了
所以使用到了CAS操作 也就是实现无锁队列

在博客的最后有无锁队列的源代码

Q: 操作系统级别是如何实现的

A: X86中有一个CMPXCHG的汇编指令

Q: CAS指令有什么缺点

A:

1.存在ABA问题因为CAS需要在操作值的时候检查下值有没有发生变化,如果没有发生变化则更新,但是如果一个值原来是A,变成了B,又变成了A,那么使用CAS进行检查时会发现它的值没有发生变化,但是实际上却变化了。ABA问题的解决思路就是使用版本号。在变量前面追加上版本号,每次变量更新的时候把版本号加一,那么A-B-A 就会变成1A-2B-3A。

2.循环时间长开销大自旋CAS如果长时间不成功,会给CPU带来非常大的执行开销。

3. 只能保证一个共享变量的原子操作对一个共享变量执行操作时,我们可以使用循环CAS的方式来保证原子操作,但是对多个共享变量操作时,循环CAS就无法保证操作的原子性,这个时候就可以用锁,或者有一个取巧的办法,就是把多个共享变量合并成一个共享变量来操作。比如有两个共享变量i=2,j=a,合并一下ij=2a,然后用CAS来操作ij。

 与CAS相关的一些原子操作

gcc从4.1.2提供了__sync_*系列的built-in函数,用于提供加减和逻辑运算的原子操作。

其声明如下:

原子操作的后置加加type __sync_fetch_and_add (type *ptr, type
value, ...)

原子操作的前置加加type __sync_add_and_fetch (type
*ptr, type value, ...)
其他类比

type __sync_fetch_and_sub (type *ptr, type
value, ...)
type __sync_fetch_and_or (type *ptr, type value, ...)
type __sync_fetch_and_and (type *ptr, type value, ...)
type __sync_fetch_and_xor (type *ptr, type value, ...)
type __sync_fetch_and_nand (type *ptr, type value, ...)

type __sync_sub_and_fetch (type *ptr, type value, ...)
type __sync_or_and_fetch (type *ptr, type value, ...)
type __sync_and_and_fetch (type *ptr, type value, ...)
type __sync_xor_and_fetch (type *ptr, type value, ...)
type __sync_nand_and_fetch (type *ptr, type value, ...)
这两组函数的区别在于第一组返回更新前的值,第二组返回更新后的值。

无锁队列源代码

为了有一个对比

写了一份thread_queue.c是用锁对临界区进行控制访问的,也就是有锁队列

另一份是lock_free_queue.c是用CAS确保对临界区的操作是原子操作,也就是无锁队列

依赖关系
lock_free_queue.c -> queue.h -> queue.c
thread_queue.c -> queue.h -> queue.c

gcc lock_free_queue.c queue.c -lpthread -o lock_free_queue
gcc thread_queue.c queue.c -lpthread -o thread_queue

  1. #ifndef QUEUE_H_
  2. #define QUEUE_H_
  3.  
  4. #include <stdio.h>
  5. #include <stdlib.h>
  6.  
  7. /*
  8. 普通的
  9. 链式队列
  10. */
  11. typedef struct QNode
  12. {
  13. int data;
  14. struct QNode *next;
  15. }QNode, *QueuePtr;
  16.  
  17. typedef struct LinkQueue
  18. {
  19. QueuePtr front;
  20. QueuePtr rear;
  21. }LinkQueue;
  22.  
  23. void init_Queue(LinkQueue *q);//初始化队列
  24. void push_Queue(LinkQueue *q, int e);//队尾入队
  25. int pop_Queue(LinkQueue *q, int *e);//队头出队
  26. int is_Empty(LinkQueue *q);
  27. void show(LinkQueue *q);
  28.  
  29. #endif /* QUEUE_H_ */

queue.h

  1. #include "queue.h"
  2.  
  3. /*
  4. 初始化
  5. 为队列构建一个头结点
  6. 让front和rear都指向这个头结点
  7. */
  8. void init_Queue(LinkQueue *q)
  9. {
  10. q->front = q->rear = (QNode *)malloc(sizeof(QNode));
  11. q->front->next = NULL;
  12. }
  13.  
  14. /*
  15. 普通的入队操作
  16. */
  17. void push_Queue(LinkQueue *q, int e)
  18. {
  19. QueuePtr newNode = (QueuePtr)malloc(sizeof(QNode));
  20. newNode->data = e;
  21. newNode->next = NULL;
  22. q->rear->next = newNode;
  23. q->rear = newNode;
  24. }
  25.  
  26. /*
  27. cas的入队操作
  28. 和普通的入队操作一样
  29. 新建节点后
  30. 要将新节点挂在队尾时需要进行cas操作
  31. */
  32. void cas_push(LinkQueue *q, int e)
  33. {
  34. QueuePtr newNode = (QueuePtr)malloc(sizeof(QNode));
  35. newNode->data = e;
  36. newNode->next = NULL;
  37.  
  38. QueuePtr tmp;
  39. do
  40. {
  41. tmp = q->rear;
  42. }while (!__sync_bool_compare_and_swap((&(tmp->next)), NULL, (newNode));
  43.  
  44. q->rear = newNode;
  45. }
  46.  
  47. /*
  48. 以前的判空是 q->front == q->rear
  49. 但是这样子会增加出队的操作 当出的是最后一个元素时, q->rear需要指向 q->front
  50. 我把这一步省了 暂时没有发现有什么副作用
  51. 所以我改成了 q->front->next == NULL
  52. */
  53. int is_Empty(LinkQueue *q)
  54. {
  55. if (q->front->next == NULL)
  56. {
  57. return();
  58. }
  59. return();
  60. }
  61.  
  62. /*
  63. 普通的出队操作
  64. 如果队空 返回0 也就是false
  65. e作为接受元素的缓冲
  66. */
  67. int pop_Queue(LinkQueue *q, int *e)
  68. {
  69. if (is_Empty(q))
  70. {
  71. return();
  72. }
  73. QueuePtr tmp;
  74. tmp = q->front->next;
  75. q->front->next = tmp->next;
  76.  
  77. *e = tmp->data;
  78. free(tmp);
  79. return();
  80. }
  81.  
  82. /*
  83. cas的出队操作
  84. 每一次都要判断这个队列是不是空
  85. 然后执行cas的出队操作:
  86. (1)tmp = q->front 把旧的队头存起来
  87. (2)执行原子操作:看 旧的队头 是否等于 现在的队头 tmp == *(&(q->front->next)) 如果相等执行 *(&(q->front->next)) = tmp->next 返回true
  88. 否则,即执行这一步原子操作的时候,别的线程修改了队列,导致队尾指向改变了,返回false ,while(!false)回到第一步重新执行
  89. */
  90. int cas_pop(LinkQueue *q, int *e)
  91. {
  92. QueuePtr tmp;
  93. do {
  94. if (is_Empty(q))
  95. {
  96. return();
  97. }
  98. tmp = q->front;
  99. } while (!__sync_bool_compare_and_swap(&(q->front->next), tmp, tmp->next->next);
  100.  
  101. *e = tmp->data;
  102. free(tmp);
  103. return();
  104. }
  105.  
  106. /*
  107. 遍历队列 打印里面的元素 为了求证队列里面的元素
  108. */
  109. void show(LinkQueue *q)
  110. {
  111. printf("void show(LinkQueue *q)\n");
  112. QueuePtr tmp = q->front->next;
  113. while (tmp)
  114. {
  115. printf("%d ", tmp->data);
  116. tmp = tmp->next;
  117. }
  118. printf("\n");
  119. }

queue.c

  1. #include "queue.h"
  2. #include <pthread.h>
  3. #include <unistd.h>
  4. #include <assert.h>
  5.  
  6. #define THREAD_NUMBER 4//开启的线程数,电脑是4核,所以用4
  7.  
  8. void *thread_push(void *arg);
  9. void *thread_pop(void *arg);
  10.  
  11. /*
  12. 初始化空队列
  13.  
  14. 为了模拟线程对资源的抢占
  15. 开启4个线程 每个线程push 20个元素 0~19
  16. 等待4个线程结束
  17. 打印队列元素 验证push
  18. 开启四个线程 每个线程都对队列进行 pop操作
  19. */
  20. int main()
  21. {
  22. LinkQueue que;
  23. init_Queue(&que);
  24.  
  25. int i;
  26. /*
  27. 创造四个新线程 每个线程都执行 thread_push(&que)
  28. */
  29. pthread_t threadArr[THREAD_NUMBER];
  30. for (i = ; i < THREAD_NUMBER; ++i)
  31. {
  32. pthread_create(&threadArr[i], NULL, thread_push, (void *)&que);
  33. }
  34.  
  35. /*
  36. 等待四个线程都执行完
  37. 要不然主线程一下子就跑完了 程序就结束了
  38. 还有就是 为了show函数 可以验证元素是不是都push进去了
  39. */
  40. for (i = ; i < THREAD_NUMBER; ++i)
  41. {
  42. pthread_join(threadArr[i], NULL);
  43. }
  44.  
  45. show(&que);
  46.  
  47. /*
  48. 创造四个新线程 每个线程都执行 thread_pop(&que)
  49. */
  50. for (i = ; i < THREAD_NUMBER; ++i)
  51. {
  52. pthread_create(&threadArr[i], NULL, thread_pop, (void *)&que);
  53. }
  54.  
  55. for (i = ; i < THREAD_NUMBER; ++i)
  56. {
  57. pthread_join(threadArr[i], NULL);
  58. }
  59.  
  60. exit(EXIT_SUCCESS);
  61. }
  62.  
  63. void *thread_push(void *arg)
  64. {
  65. printf("start push\n");
  66. LinkQueue * quePtr = (LinkQueue *)arg;
  67. int i;
  68. for (i = ; i < ; ++i)
  69. {
  70. cas_push(quePtr, i);
  71. }
  72. printf("finish push\n");
  73. pthread_exit(NULL);
  74. }
  75.  
  76. void *thread_pop(void *arg)
  77. {
  78. printf("start pop\n");
  79. LinkQueue * quePtr = (LinkQueue *)arg;
  80. int tmp;
  81. int res;
  82. while ()
  83. {
  84. res = cas_pop(quePtr, &tmp);
  85. if (!res)
  86. {
  87. break;
  88. }
  89. printf("%d ", tmp);
  90. }
  91. printf("finish pop\n");
  92. pthread_exit(NULL);
  93. }

lock_free_queue.c

  1. #include "queue.h"
  2. #include <pthread.h>
  3. #include <unistd.h>
  4. #include <semaphore.h>
  5. #include <assert.h>
  6.  
  7. #define THREAD_NUMBER 4
  8.  
  9. sem_t queue_sem;//同步锁
  10.  
  11. void *thread_push(void *arg);
  12. void *thread_pop(void *arg);
  13.  
  14. int main()
  15. {
  16. LinkQueue que;
  17. init_Queue(&que);
  18.  
  19. /*初始化二进制信号量 初始值为1 代表每一次只有1个线程可以访问
  20. 本来更加应该用互斥量 比较贴合情景 但是不太熟 就用了信号量
  21. */
  22. int res = sem_init(&queue_sem, , );
  23. assert(res != -);
  24.  
  25. int i;
  26. pthread_t threadArr[THREAD_NUMBER];
  27. for (i = ; i < THREAD_NUMBER; ++i)
  28. {
  29. pthread_create(&threadArr[i], NULL, thread_push, (void *)&que);
  30. }
  31.  
  32. for (i = ; i < THREAD_NUMBER; ++i)
  33. {
  34. pthread_join(threadArr[i], NULL);
  35. }
  36.  
  37. show(&que);
  38.  
  39. for (i = ; i < THREAD_NUMBER; ++i)
  40. {
  41. pthread_create(&threadArr[i], NULL, thread_pop, (void *)&que);
  42. }
  43.  
  44. for (i = ; i < THREAD_NUMBER; ++i)
  45. {
  46. pthread_join(threadArr[i], NULL);
  47. }
  48.  
  49. sem_destroy(&queue_sem);
  50.  
  51. exit(EXIT_SUCCESS);
  52. }
  53.  
  54. void *thread_push(void *arg)
  55. {
  56. printf("start push\n");
  57. LinkQueue * quePtr = (LinkQueue *)arg;
  58. int i;
  59. for (i = ; i < ; ++i)
  60. {
  61. sem_wait(&queue_sem);
  62. push_Queue(quePtr, i);
  63. sem_post(&queue_sem);
  64. }
  65. printf("finish push\n");
  66. pthread_exit(NULL);
  67. }
  68.  
  69. void *thread_pop(void *arg)
  70. {
  71. printf("start pop\n");
  72. LinkQueue * quePtr = (LinkQueue *)arg;
  73. int tmp;
  74. int res;
  75. while ()
  76. {
  77. sem_wait(&queue_sem);
  78. res = pop_Queue(quePtr, &tmp);
  79. sem_post(&queue_sem);
  80. if (!res)
  81. {
  82. break;
  83. }
  84. printf("%d ", tmp);
  85. }
  86. printf("finish pop\n");
  87. pthread_exit(NULL);
  88. }

thread_queue.c

博客,参考:http://blog.csdn.net/syzcch/article/details/8075830

libcds库,参考:http://blog.jobbole.com/90810/

CAS缺点,参考:https://www.cnblogs.com/zhuawang/p/4196904.html

CAS函数,参考: http://blog.csdn.net/youfuchen/article/details/23179799

gcc Atomic Builtins官方:https://gcc.gnu.org/onlinedocs/gcc-4.1.1/gcc/Atomic-Builtins.html

CAS简介和无锁队列的实现的更多相关文章

  1. 锁、CAS操作和无锁队列的实现

    https://blog.csdn.net/yishizuofei/article/details/78353722 锁的机制 锁和人很像,有的人乐观,总会想到好的一方面,所以只要越努力,就会越幸运: ...

  2. boost 无锁队列

    一哥们翻译的boost的无锁队列的官方文档 原文地址:http://blog.csdn.net/great3779/article/details/8765103 Boost_1_53_0终于迎来了久 ...

  3. Erlang运行时中的无锁队列及其在异步线程中的应用

    本文首先介绍 Erlang 运行时中需要使用无锁队列的场合,然后介绍无锁队列的基本原理及会遇到的问题,接下来介绍 Erlang 运行时中如何通过“线程进度”机制解决无锁队列的问题,并介绍 Erlang ...

  4. 无锁队列以及ABA问题

    队列是我们非常常用的数据结构,用来提供数据的写入和读取功能,而且通常在不同线程之间作为数据通信的桥梁.不过在将无锁队列的算法之前,需要先了解一下CAS(compare and swap)的原理.由于多 ...

  5. zeromq源码分析笔记之无锁队列ypipe_t(3)

    在上一篇中说到了mailbox_t的底层实际上使用了管道ypipe_t来存储命令.而ypipe_t实质上是一个无锁队列,其底层使用了yqueue_t队列,ypipe_t是对yueue_t的再包装,所以 ...

  6. 基于无锁队列和c++11的高性能线程池

    基于无锁队列和c++11的高性能线程池线程使用c++11库和线程池之间的消息通讯使用一个简单的无锁消息队列适用于linux平台,gcc 4.6以上   标签: <无>   代码片段(6)[ ...

  7. java轻松实现无锁队列

    1.什么是无锁(Lock-Free)编程 当谈及 Lock-Free 编程时,我们常将其概念与 Mutex(互斥) 或 Lock(锁) 联系在一起,描述要在编程中尽量少使用这些锁结构,降低线程间互相阻 ...

  8. 【DPDK】【ring】从DPDK的ring来看无锁队列的实现

    [前言] 队列是众多数据结构中最常见的一种之一.曾经有人和我说过这么一句话,叫做“程序等于数据结构+算法”.因此在设计模块.写代码时,队列常常作为一个很常见的结构出现在模块设计中.DPDK不仅是一个加 ...

  9. DPDK 无锁队列Ring Library原理(学习笔记)

    参考自DPDK官方文档原文:http://doc.dpdk.org/guides-20.02/prog_guide/ring_lib.html 针对自己的理解做了一些辅助解释. 1 前置知识 1.1 ...

随机推荐

  1. python3写入csv多一个空行

    今天用python3 写入csv 总是多一个空行,查阅python3文档,发现有一个参数可以设置.特此记录一下. with open('file.csv','a',newline='') as csv ...

  2. FusionCharts Marimekko图

    1.Marimekko静态页面 Marimekko.html: <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//E ...

  3. MySQL插入数据异常

    MySQL插入数据异常 1.错误如下: com.mysql.jdbc.exceptions.jdbc4.MySQLIntegrityConstraintViolationException:Dupli ...

  4. Linux查看磁盘剩余空间

    Linux查看磁盘剩余空间 youhaidong@youhaidong-ThinkPad-Edge-E545:~$ df 文件系统 1K-blocks 已用 可用 已用% 挂载点 /dev/sda8 ...

  5. Docker 小记 — Compose & Swarm

    前言 任何相对完整的应用服务都不可能是由单一的程序来完成支持,计划使用 Docker 来部署的服务更是如此.大型服务需要进行拆分,形成微服务集群方能增强其稳定性和可维护性.本篇随笔将对 Docker ...

  6. 【原】Java学习笔记028 - 集合

    package cn.temptation; import java.util.HashSet; import java.util.Set; public class Sample01 { publi ...

  7. class-支持向量机SVM全析笔记

    support vector machines,SVM是二类分类模型.定义在特征空间上间隔最大的线性分类器,由于包括核技巧实质上成为非线性分类器.学习策略是间隔最大化,可形式化为求解凸二次规划问题(c ...

  8. C#图解教程 第二十五章 其他主题

    其他主题 概述字符串使用 StringBuilder类把字符串解析为数据值关于可空类型的更多内容 为可空类型赋值使用空接合运算符使用可空用户自定义类型 Main 方法文档注释 插入文档注释使用其他XM ...

  9. javaScript替换元素节点

    <!DOCTYPE HTML> <html> <head> <meta http-equiv="Content-Type" content ...

  10. jquery实现简单的搜索

    对一个简单的ul列表进行输入框的搜索功能,搜索之前及搜索后显示效果如下: 用到的主要jquery技术有filter()和match()方法以及正则匹配,基础HTML+div设置: <div cl ...