与同步传递相关的获取-释放序列

为了考虑传递顺序,你至少需要三个线程。第一个线程用来修改共享变量,并且对其中一个 做“存储-释放”处理。然后第二个线程使用“加载-获取”读取由“存储-释放”操作过的变量,并且 再对第二个变量进行“存储-释放”操作。最后,由第三个线程通过“加载-获取”读取第二个共享变量。提供“加载-获取”操作,来读取被“存储-释放”操作写入的值,是为了保证同步关系,这里即便是中间线程没有对共享变量做任何操作,第三个线程也可以读取被第一个线程操作过 的变量。下面的代码可以用来描述这样的场景。

清单5.9 使用获取和释放顺序进行同步传递

#include <atomic>
#include <thread>
#include <assert.h>std::atomic<int> data[5];
std::atomic<bool> sync1(false), sync2(false);void thread_1()
{data[0].store(42, std::memory_order_relaxed);data[1].store(97, std::memory_order_relaxed);data[2].store(17, std::memory_order_relaxed);data[3].store(-141, std::memory_order_relaxed);data[4].store(2003, std::memory_order_relaxed);sync1.store(true, std::memory_order_release); // 1 设置sync1
}void thread_2()
{while(!sync1.load(std::memory_order_acquire)); // 2 直到sync1设置后, 循环结束sync2.store(std::memory_order_release);        // 3 设置sync2
}void thread_3()
{while(!sync2.load(std::memory_order_acquire));// 4 直到sync1设置后,循环结束assert(data[0].load(std::memory_order_relaxed) == 42);assert(data[1].load(std::memory_order_relaxed) == 97);assert(data[2].load(std::memory_order_relaxed) == 17);assert(data[3].load(std::memory_order_relaxed) == -141);assert(data[4].load(std::memory_order_relaxed) == 2003);}int main()
{std::thread t1(thread_1);std::thread t2(thread_2);std::thread t3(thread_3);t1.join();t2.join();t3.join();
}

尽管thread_2只接触到变量syn1②和sync2③,不过这对于thread_1和thread_3的同步就足够 了,这就能保证断言不会触发。首先,thread_1将数据存储到data中先行与存储sync1①(它们在同一个线程内)。因为加载sync1①的是一个while循环,它最终会看到thread_1存储的值 (是从“释放-获取”对的后半对获取)。因此,对于sync1的存储先行与最终对于sync1的加载(在 while循环中)。thread_3的加载操作④,位于存储sync2③操作的前面(也就是先行)。存储sync2③因此先行于thread_3的加载④,加载又先行与存储sync2③,存储sync2又先行与加载 sync2④,加载syn2又先行与加载data。因此,thread_1存储数据到data的操作先行于 thread_3中对data的加载,并且保证断言都不会触发。

在这个例子中,你可以将sync1和sync2,通过在thread_2中使用“读-改-写”操作 (memory_order_acq_rel),将其合并成一个独立的变量。其中会使用 compare_exchange_strong()来保证thread_1对变量只进行一次更新:

std::atomic<int> sync(0);void thread_1()
{//... sync.store(1,std::memory_order_release);
}void thread_2()
{int expected = 1;while(!sync.compare_exchange_strong(expected,2, std::memory_order_acq_rel)) {expected = 1;}}void thread_3()
{while(sync.load(std::memory_order_acquire) < 2);// ...
}

如果你使用“读-改-写”操作,选择语义就很重要了。在这个例子中,你想要同时进行获取和释 放的语义,所以memory_order_acq_rel是一个合适的选择,但你也可以使用其他序列。使用 memory_order_acquire语义的fetch_sub是不会和任何东西同步的,即使它存储了一个值,这 是因为其没有释放操作。同样的,使用memory_order_release语义的fetch_or也不会和任何 存储操作进行同步,因为对于fetch_or的读取,并不是一个获取操作。使用 memory_order_acq_rel语义的“读-改-写”操作,每一个动作都包含获取和释放操作,所以可以 和之前的存储操作进行同步,并且可以对随后的加载操作进行同步,就像上面例子中那样。

如果你将“获取-释放”操作和“序列一致”操作进行混合,“序列一致”的加载动作,就像使用了获 取语义的加载操作;并且序列一致的存储操作,就如使用了释放语义的存储。“序列一致”的 读-改-写操作行为,就像同时使用了获取和释放的操作。“自由操作”依旧那么自由,但其会和 额外的同步进行绑定(也就是使用“获取-释放”的语义)。

尽管潜在的结果并不那么直观,每个使用锁的同学都不得不去解决同一个序列问题:锁住互斥量是一个获取操作,并且解锁这个互斥量是一个释放操作。随着互斥量的增多,你必须确保同一个互斥量在你读取变量或修改变量的时候是锁住的,并且同样适合于这里;你的获取 和释放操作必须在同一个变量上,以保证访问顺序。当数据被一个互斥量所保护时,锁的性 质就保证得到的结果是没有区别的,因为锁住与解锁的操作都是序列一致的操作。同样的, 当你对原子变量使用获取和释放序列,为的是构建一个简单的锁,那么这里的代码必然要使 用锁,即使内部操作不是序列一致的,其外部表现将会是序列一致的。

当你的原子操作不需要严格的序列一致序列,成对同步的“获取-释放”序列可以提供,比全局序列一致性操作,更加低廉的潜在同步。这里还需要对心理代价进行权衡,为了保证序列能 够正常的工作,还要保证非直观的跨线程行为是没有问题的。

与同步传递相关的获取-释放序列相关推荐

  1. Python爬虫笔记——分析AJAX传递的JSON获取数据-初步分析动态网页

    转载文章链接: Python爬虫:分析AJAX传递的JSON获取数据-初步分析动态网页(1) [4]实战:爬取动态网页的两种思路爬取新浪趣图(1) [5]实战:爬取动态网页的两种思路爬取新浪趣图(2) ...

  2. JNI内存方面说明以及相关类型手动释放内存

    JNI内存方面说明以及相关类型手动释放内存 一.Java内存 二.JNI内存和引用 三.Local Reference 四.Global Reference 五.Weak Global Referen ...

  3. Mybatis 获取当前序列和下一个序列值 以及在一个方法中写多条SQL 语句

    目录 1.Mybatis 获取当前序列和下一个序列值 2.Mybatis 在一个方法中写多条SQL 语句 1.Mybatis 获取当前序列和下一个序列值 #获取当前序列值 select XXX_seq ...

  4. Android开发 Intent传递参数,获取数据为null

    android开发 Activity通过Intent传递参数,获取失败的原因 启动一个有返回值的activity (MapActivity) Intent intent = new Intent(Up ...

  5. android获取到电信的手机号码,Android基站信息获取以及Sim卡相关信息获取

    概述: 本篇主要介绍Android获取基站信息的方式,除此之外,还有SIM卡相关字段获取,先介绍一些缩写的概念,后续更新代码的写法. 前言:之前有碰到一个需求,需要获取SIM卡的相关属性:IMSI号. ...

  6. Altium软件以及库相关资源获取

    主要介绍Altium相关资源获取,只是一些个人比较常用的资源分享 软件资源获取(不分先后): 1.官网 https://www.altium.com.cn/ 2.吴大大的博客 https://www. ...

  7. Chrome 插件开发-主动获取所有页签的tabid,background.js直接向所有页签同步传递消息

    有时候 backgroud.js 需要向所有的页签同时同步消息,这时就要获取到所有页签的 tabid 了,下面的方法即可实现. function open_all_tab(){// 获取所有的页签ch ...

  8. 腾讯邓君:《王者荣耀》翻过的同步技术相关的三座大山

    在Unity举办的Unite 2017上海开发者大会中,案例分享专场受到了业内的高度关注,众多行业领军人物针对过往的成功案例慷慨的分享了开发过程中的经验教训,而<王者荣耀>项目组技术总监邓 ...

  9. Java笔记-多线程中同步加锁相关

    Java程序入口就是由JVM启动的main线程: main线程又可以启动其他线程.当所有线程都运行结束时JVM退出,进程结束. 守护线程(Daemon):守护线程是为其他线程服务的线程,所有的非守护线 ...

最新文章

  1. 无人出租要遍地,Waymo百度这种报告就得常走起
  2. python基础教程 pdf github_python基础教程之Jupyter导出PDF从入门到绝望(已解|python基础教程|python入门|python教程...
  3. java2第九章的总结_java并发的艺术-读书笔记-第九章线程池
  4. 获取执行计划的N种方式
  5. 服务器根没有web文件系统,Web服务器
  6. 图灵机二义性_编译原理知识汇总
  7. html中span不显示背景
  8. Kotlin Weekly 中文周报 —— 16
  9. GitLab(三)创建用户
  10. C语言程序设计第六次作业
  11. html双引号打不正确,双引号要占一个空格吗 为什么打双引号要空格一下才能出来...
  12. 图片在相应页面变化的时候拉长
  13. 手风琴控件android,手风琴控件 | Accordion Control
  14. python中%代表什么意思?
  15. linux 抓图,关于Linux下的抓图软件和使用方法介绍
  16. ArcGIS基于爬虫数据绘制人口分布密度图
  17. Redis监控和预警
  18. TLD7002学习笔记(一)-芯片介绍
  19. MT7621处理器资料解析,MT7621数据表
  20. FC小霸王4000余款游戏整合版图文说明

热门文章

  1. msyql之解决mysql出现ERROR 1698 (28000): Access denied的问题
  2. Halcon Blob分析(二值化图像分割)
  3. Ruby小白入门笔记之Rubymine工具的快捷键
  4. IDEA整合Spring Boot项目访问jsp文件
  5. 智慧城市近两年来受到国家高度重视
  6. android安全攻防实践_Android安全攻防实战 PDF 下载
  7. 以敏捷的方式运作一所大学
  8. Win10使用sh执行python脚本报错:Permission denied
  9. android工具栏设为底层,Android 隐藏底部工具栏
  10. swf php文本,SWFFont - PHP 5 中文文档