当前位置: 首页 > news >正文

java 加减乘除是原子操作吗_Go并发编程之传统同步—(3)原子操作

前言

之前文章中介绍的互斥锁虽然能够保证同串行化,但是却保证不了执行过程中的中断。

要么成功、要么失败,没有中断的情况,我们叫它叫原子性,这种由硬件 CPU 提供支持的特性,是非常可靠的。

百度百科上关于原子操作的介绍。

原子操作

由 sync/atomic 包提供操作支持。

加法(add)

实现累加func TestDemo1(t *testing.T) {

var counter int64 = 0

for i := 0; i < 100; i++ {

go func() {

atomic.AddInt64(&counter, 1)

}()

}

time.Sleep(2 * time.Second)

log.Println("counter:", atomic.LoadInt64(&counter))

}

结果=== RUN TestDemo1

2020/10/11 00:24:56 counter: 100

--- PASS: TestDemo1 (2.00s)

PASS

减法(add)

对于做减法,是没有直接提供的方法的,而 Add(-1)这种是不能对 uint 类型使用的,可以通过补码的方式实现func TestDemo2(t *testing.T) {

var counter uint64 = 100

for i := 0; i < 100; i++ {

go func() {

atomic.AddUint64(&counter, ^uint64(-(-1)-1))

}()

}

time.Sleep(2 * time.Second)

log.Println("counter:", atomic.LoadUint64(&counter))

}

结果=== RUN TestDemo2

2020/10/11 00:32:05 counter: 0

--- PASS: TestDemo2 (2.00s)

PASS

比较并交换(compare and swap,简称 CAS)

并发编程中,在没有使用互斥锁的前提下,对共享数据先取出做判断,再根据判断的结果做后续操作,必然是会出问题的,使用 CAS 可以避免这种问题。func TestDemo3(t *testing.T) {

var first int64 = 0

for i := 1; i <= 10000; i++ {

go func(i int) {

if atomic.CompareAndSwapInt64(&first, 0, int64(i)) {

log.Println("抢先运行的是 goroutine", i)

}

}(i)

}

time.Sleep(2 * time.Second)

log.Println("num:", atomic.LoadInt64(&first))

}

结果=== RUN TestDemo3

2020/10/11 00:42:10 抢先运行的是 goroutine 3

2020/10/11 00:42:12 num: 3

--- PASS: TestDemo3 (2.01s)

PASS

加载(load)

加载操作在进行时只会有一个,不会有其它的读写操作同时进行。func TestDemo4(t *testing.T) {

var counter int64 = 0

for i := 0; i < 100; i++ {

go func() {

atomic.AddInt64(&counter, 1)

log.Println("counter:", atomic.LoadInt64(&counter))

}()

}

time.Sleep(2 * time.Second)

}

存储(store)

存储操作在进行时只会有一个,不会有其它的读写操作同时进行。func TestDemo5(t *testing.T) {

var counter int64 = 0

for i := 0; i < 10; i++ {

go func(i int) {

atomic.StoreInt64(&counter, int64(i))

log.Println("counter:", atomic.LoadInt64(&counter))

}(i)

}

time.Sleep(2 * time.Second)

}

交换(swap)

swap 方法返回被替换之前的旧值。func TestDemo6(t *testing.T) {

var counter int64 = 0

for i := 0; i < 10; i++ {

go func(i int) {

log.Println("counter old:", atomic.SwapInt64(&counter, int64(i)))

}(i)

}

time.Sleep(2 * time.Second)

}

结果=== RUN TestDemo6

2020/10/11 00:43:36 counter old: 0

2020/10/11 00:43:36 counter old: 9

2020/10/11 00:43:36 counter old: 5

2020/10/11 00:43:36 counter old: 1

2020/10/11 00:43:36 counter old: 2

2020/10/11 00:43:36 counter old: 3

2020/10/11 00:43:36 counter old: 6

2020/10/11 00:43:36 counter old: 4

2020/10/11 00:43:36 counter old: 7

2020/10/11 00:43:36 counter old: 0

--- PASS: TestDemo6 (2.00s)

PASS

原子值(value)

value是一个结构体,内部值定义为 interface{},所以它是可以接受任何类型的值。

第一次赋值的时候,原子值的类型就确认了,后面不能赋值其它类型的值。func TestDemo7(t *testing.T) {

var value atomic.Value

var counter uint64 = 1

value.Store(counter)

log.Println("counter:", value.Load())

value.Store(uint64(10))

log.Println("counter:", value.Load())

value.Store(100) // 引发 panic

log.Println("counter:", value.Load())

time.Sleep(2 * time.Second)

}

结果=== RUN TestDemo7

2020/10/11 10:14:58 counter: 0

2020/10/11 10:14:58 counter: 10

--- FAIL: TestDemo7 (0.00s)

panic: sync/atomic: store of inconsistently typed value into Value [recovered]

panic: sync/atomic: store of inconsistently typed value into Value

...

Process finished with exit code 1

扩展

无锁编程

此处暂时先介绍一下,后面有机会出文章再一起学习进步。

放弃互斥锁,采用原子操作,常见方法有以下几种:

针对计数器

可以使用例如上面介绍的 Add 方法。

单生产、消费者

单生产者、单消费者可以做到免锁访问环形缓冲区(Ring Buffer)。

比如,Linux kernel 中的 kfifo 的实现。

RCU(Read Copy Update)

新旧副本切换机制,对于旧副本可以采用延迟释放的做法。

CAS(Compare And Swap)

如无锁栈,无锁队列等待

总结原子操作性能是高于互斥锁的,但带来的复杂性也会提高,真正用好并不容易。

互斥锁、条件变量,方法内部的实现也都用到了原子操作,特别是CAS。

有疑问加站长微信联系(非本文作者)

相关文章:

  • 毕业了
  • mysql innodb 删除_MySQL InnoDB 删除资料后释放硬盘空间
  • request变量java jsp_JSP里request变量列表
  • transition java_Transition 过渡
  • 相对最完整的软件测试工具手册
  • 上传图片并且生成可以控制大小图片清晰度的方法
  • 手机php开发环境,PHP开发环境搭建
  • 要不要把php5升级到php7,将php5升级到php7后AJAX不工作
  • [软工]近距离接触RUP plug-in
  • zblog asp 转 php,怎么把zblog asp 2.2转换成zblog php 1.5的方法
  • 扩展XDoclet对Spring List引用注入的支持
  • wifidog php,用php写wifidog的认证服务器
  • 3668MySQL数据库应用试题,django框架mysql数据库使用常遇问题
  • matlab中为什么有的坐标右面也有刻度,Matlab绘图笔记:修改坐标轴显示的刻度密度,lable文字,和位置...
  • 首博!
  • ES6指北【2】—— 箭头函数
  • android 一些 utils
  • angular组件开发
  • HTTP那些事
  • Java 多线程编程之:notify 和 wait 用法
  • Javascript 原型链
  • java中的hashCode
  • October CMS - 快速入门 9 Images And Galleries
  • SQLServer之创建数据库快照
  • vue自定义指令实现v-tap插件
  • Yii源码解读-服务定位器(Service Locator)
  • 电商搜索引擎的架构设计和性能优化
  • 湖南卫视:中国白领因网络偷菜成当代最寂寞的人?
  • 如何将自己的网站分享到QQ空间,微信,微博等等
  • 突破自己的技术思维
  • 网页视频流m3u8/ts视频下载
  • 微信小程序:实现悬浮返回和分享按钮
  • 一文看透浏览器架构
  • 移动端 h5开发相关内容总结(三)
  • 不要一棍子打翻所有黑盒模型,其实可以让它们发挥作用 ...
  • ​Linux·i2c驱动架构​
  • ​卜东波研究员:高观点下的少儿计算思维
  • ​业务双活的数据切换思路设计(下)
  • ###51单片机学习(2)-----如何通过C语言运用延时函数设计LED流水灯
  • #14vue3生成表单并跳转到外部地址的方式
  • #我与Java虚拟机的故事#连载15:完整阅读的第一本技术书籍
  • (1)(1.11) SiK Radio v2(一)
  • (1)SpringCloud 整合Python
  • (11)MATLAB PCA+SVM 人脸识别
  • (保姆级教程)Mysql中索引、触发器、存储过程、存储函数的概念、作用,以及如何使用索引、存储过程,代码操作演示
  • (论文阅读笔记)Network planning with deep reinforcement learning
  • (转)AS3正则:元子符,元序列,标志,数量表达符
  • (转)http-server应用
  • (转)ObjectiveC 深浅拷贝学习
  • ***检测工具之RKHunter AIDE
  • *2 echo、printf、mkdir命令的应用
  • .NET Remoting Basic(10)-创建不同宿主的客户端与服务器端
  • .net 打包工具_pyinstaller打包的exe太大?你需要站在巨人的肩膀上-VC++才是王道
  • .NET 读取 JSON格式的数据
  • .net 无限分类