Go语言并发之原子操作详解

目录
  • 修改
  • 赋值与读取
  • 比较并交换
  • 小结

代码中的加锁操作因为涉及内核态的上下文切换会比较耗时、代价比较高。针对基本数据类型我们还可以使用原子操作来保证并发安全,因为原子操作是Go语言提供的方法它在用户态就可以完成,因此性能比加锁操作更好。Go语言中原子操作由内置的标准库sync/atomic 提供。

大多数情况下我们都是针对基本数据类型进行数据操作,能不加锁就不加锁。

首先很多人都不相信基本类型并发修改会出现竞态问题。不妨尝试一下,并发加一。

var wg sync.WaitGroup
for i := 0; i < 10000; i++ {
wg.Add(1)
go func () {
defer wg.Done()
xInt32++
}()
}
wg.Wait()
print(xInt32)

无论输出多少次都无法达到10000,之所以如此就是因为此处的加1操作并不是原子的,都是先取当前值,加1,再赋值,会出现覆盖的情况。

修改

修改是最常用到的。

func modify(delta int32) {
atomic.AddInt32(&xInt32, delta)
atomic.AddInt64(&xInt64, int64(delta))
atomic.AddUint32(&xuInt32, uint32(delta))
atomic.AddUint64(&xuInt64, uint64(delta))
}

我们忽略了Uintptr的讨论,这是内存地址的整数表示,是用来存地址内容的,暂时没有遇到过指针的数据计算。

var wg sync.WaitGroup
for i := 0; i < 10000; i++ {
wg.Add(1)
go func () {
defer wg.Done()
//xInt32++
modify(1)
}()
}
wg.Wait()
print(xInt32)

改为原子操作后,发现每次运行都可以得到预期的结果10000

赋值与读取

在并发情况下,读取到某个变量后,在使用时变量内容可能会被篡改,所以使用原子读取。 在并发情况下,为某个变量赋值的时候,必须要防止读取到写入一半的错误值,所以要用原子写入。

var xInt32 int32
atomic.StoreInt32(&xInt32, 100)
println(xInt32)
v := atomic.LoadInt32(&xInt32)
println(v)

输出

100
100

就目前而言,原子读写都是为了防止读写一半导致数据错误,但我无法复现这种错误的场景,假如你可以复现请在本文底部放留言。

var v atomic.Value
v.Store([]int{})
fmt.Println(v.Load().([]int))

也可以存储其他任意类型,但如果使用到类似append扩容原变量的语句,而不是使用直接替换的话,原子操作也是会失效的。

比较并交换

以下是节选自《Go并发编程实战》一书中的例子,比较并交换(Compare And Swap)简称CAS,是乐观锁的核心思想,所以简单介绍一下。

var xInt32 int32
for {
    v := atomic.LoadInt32(&xInt32)
    if atomic.CompareAndSwapInt32(&xInt32, v, v+100) {
        break
    }
}
print(xInt32)
  • 这里一种无锁的结构,是一种思路,在需要改变数据的时候,反复判断数据是否和原数据一致
  • 一致时替换,不一致时说明被它处修改,则跳过
  • 在不创建互斥量和不形成临界区的情况下,完成并发安全的值替换操作。

小结

1.最常用原子操作中的修改、基本类型的值赋值,其他不常用

2.在其他类型出现并发的时候尽可能使用sync包提供的并发安全的类型,下一节讲。

3.通过通信共享内存;不要通过共享内存进行通信。尽量使用通道。

到此这篇关于Go语言并发之原子操作详解的文章就介绍到这了,更多相关Go语言原子操作内容请搜索我们以前的文章或继续浏览下面的相关文章希望大家以后多多支持我们!

(0)

相关推荐

  • Golang使用lua脚本实现redis原子操作

    目录 [redis 调用Lua脚本](#redis 调用Lua脚本) [redis+lua 实现评分排行榜实时更新](#redis+lua 实现评分排行榜实时更新) [lua 脚本](#lua 脚本) Golang调用redis+lua示例 byte切片与string的转换优化 redis 调用Lua脚本 EVAL命令 redis调用Lua脚本需要使用EVAL命令. redis EVAL命令格式: redis 127.0.0.1:6379> EVAL script numkeys key [ke

  • goland -sync/atomic原子操作小结

    目录 1.go已经提供了锁,为什么还需要atomic原子操作? 2.atomic原子操作为什么比mutex快? 3.CAS 4.互斥锁与原子操作区别 5.原子操作方法 5.1 atomic.AddInt32--增减 5.2 CAS-atomic.CompareAndSwapInt32--比较并替换 5.3 atomic.StoreInt32--写操作 5.4 atomic.LoadInt32--读操作 5.5 atomic.SwapInt32--直接交换 1.go已经提供了锁,为什么还需要ato

  • Go语言原子操作及互斥锁的区别

    目录 增或减 比较并交换(Compare And Swap) 载入与存储 交换 原子值 原子操作与互斥锁的区别 原子操作就是不可中断的操作,外界是看不到原子操作的中间状态,要么看到原子操作已经完成,要么看到原子操作已经结束.在某个值的原子操作执行的过程中,CPU绝对不会再去执行其他针对该值的操作,那么其他操作也是原子操作. Go语言中提供的原子操作都是非侵入式的,在标准库代码包sync/atomic中提供了相关的原子函数. 增或减 用于增或减的原子操作的函数名称都是以"Add"开头的,

  • Go语言并发之原子操作详解

    目录 修改 赋值与读取 比较并交换 小结 代码中的加锁操作因为涉及内核态的上下文切换会比较耗时.代价比较高.针对基本数据类型我们还可以使用原子操作来保证并发安全,因为原子操作是Go语言提供的方法它在用户态就可以完成,因此性能比加锁操作更好.Go语言中原子操作由内置的标准库sync/atomic 提供. 大多数情况下我们都是针对基本数据类型进行数据操作,能不加锁就不加锁. 首先很多人都不相信基本类型并发修改会出现竞态问题.不妨尝试一下,并发加一. var wg sync.WaitGroup for

  • Java微服务开发之Swagger详解

    目录 一.Swagger的作用和概念 1.Swagger 的优势 2.SwaggerUI 特点 2.SpringBoot集成Swagger 3.配置Swagger 4.实体配置 5.其他皮肤 一.Swagger的作用和概念 ​ 官方地址:https://swagger.io/ ​ Swagger 是一个规范且完整的框架,用于生成.描述.调用和可视化 RESTful 风格的 Web 服务以及 集成Swagger自动生成API文档. ​ Swagger 的目标是对 REST API 定义一个标准且和

  • java 多线程与并发之volatile详解分析

    目录 CPU.内存.缓存的关系 CPU缓存 什么是CPU缓存 为什么要有多级CPU Cache Java内存模型(Java Memory Model,JMM) JMM导致的并发安全问题 可见性 原子性 有序性 volatile volatile特性 volatile 的实现原理 总结 CPU.内存.缓存的关系 要理解JMM,要先从计算机底层开始,下面是一份大佬的研究报告 计算机在做一些我们平时的基本操作时,需要的响应时间是不一样的!如果我们计算一次a+b所需要的的时间: CPU读取内存获得a,1

  • 基于JS脚本语言的基础语法详解

    JS脚本语言的基础语法:输出语法  alert("警告!");  confirm("确定吗?");   prompt("请输入密码");为弱类型语言: 开始时要嵌入JS代码:<script type="text/javascript"></script>: 关于写程序是需注意的基本语法: 1.所有的字符全都是英文半角的: 2.大部分情况下每条语句结束后要加分号: 3.每一块代码结束后加换行:4.程序前呼

  • Linux 下C语言连接mysql实例详解

    Linux 下C语言连接mysql实例详解 第一步: 安装mysql, 参考:http://www.jb51.net/article/39190.htm 第二步: 安装mysql.h函数库 sudo apt-get install libmysqlclient-dev 执行之后就可以看到/usr/include/MySQL目录了 然后开始我们的链接. 首先看我的数据库 mysql> show databases; +--------------------+ | Database | +----

  • C语言文件复制实例详解

    C语言文件复制实例详解 文件复制,在Linux中,将生成的read.o 重新文件拷贝一份复制到ReadCopy.o中,并且更改ReadCopy.o文件的操作权限.使其能够正常运行. 实例代码: #include <stdio.h> int main(){ FILE *r_file = fopen ("read.o","rb"); FILE *w_file = fopen ("ReadCopy.o","w"); ch

  • C++语言实现hash表详解及实例代码

    C++语言实现hash表详解 概要: hash表,有时候也被称为散列表.个人认为,hash表是介于链表和二叉树之间的一种中间结构.链表使用十分方便,但是数据查找十分麻烦:二叉树中的数据严格有序,但是这是以多一个指针作为代价的结果.hash表既满足了数据的查找方便,同时不占用太多的内容空间,使用也十分方便. 打个比方来说,所有的数据就好像许许多多的书本.如果这些书本是一本一本堆起来的,就好像链表或者线性表一样,整个数据会显得非常的无序和凌乱,在你找到自己需要的书之前,你要经历许多的查询过程:而如果

  • C语言 全局变量和局部变量详解及实例

    C语言 全局变量和局部变量详解 核心内容: 1.局部变量和全局变量 变量按照作用域分为:全局变量和局部变量 全局变量的作用域:从定义位置开始到下面整个程序结束. 局部变量的作用域:在一个函数内部定义的变量只能在本函数内部进行使用. OK,上面的效果用Java语言实现一下: public class App1 { public static int k = 10;//相当于全局变量 public static void main(String[] args) { int i = 10;//局部变量

  • JVM内存管理之JAVA语言的内存管理详解

    引言 内存管理一直是JAVA语言自豪与骄傲的资本,它让JAVA程序员基本上可以彻底忽略与内存管理相关的细节,只专注于业务逻辑.不过世界上不存在十全十美的好事,在带来了便利的同时,也因此引入了很多令人抓狂的内存溢出和泄露的问题. 可怕的事情还不只如此,有些使用其它语言开发的程序员,给JAVA程序员扣上了一个"不懂内存"的帽子,这着实有点让人难以接受.毕竟JAVA当中没有malloc和delete.没有析构函数.没有指针,刚开始接触JAVA的程序员们又怎么可能接触内存这一部分呢,更何况有不

  • C语言柔性数组实例详解

    本文实例分析了C语言柔性数组的概念及用法,对于进一步学习C程序设计有一定的借鉴价值.分享给大家供大家参考.具体如下: 一般来说,结构中最后一个元素允许是未知大小的数组,这个数组就是柔性数组.但结构中的柔性数组前面必须至少一个其他成员,柔性数组成员允许结构中包含一个大小可变的数组,sizeof返回的这种结构大小不包括柔性数组的内存.包含柔数组成员的结构用malloc函数进行内存的动态分配,且分配的内存应该大于结构的大小以适应柔性数组的预期大小.柔性数组到底如何使用? 不完整类型 C和C++对于不完

随机推荐