这个系列的文章里介绍了很多并发编程里经常用到的技术,除了Context、计时器、互斥锁还有通道外还有一种技术--原子操作在一些同步算法中会被用到。今天的文章里我们会简单了解一下Go语言里对原子操作的支持,然后探讨一下原子操作和互斥锁的区别。

文章的主要话题如下:

  • 原子操作
  • Go对原子操作的支持
  • 原子操作和互斥锁的区别

原子操作

原子操作即是进行过程中不能被中断的操作,针对某个值的原子操作在被进行的过程中,CPU绝不会再去进行其他的针对该值的操作。为了实现这样的严谨性,原子操作仅会由一个独立的CPU指令代表和完成。原子操作是无锁的,常常直接通过CPU指令直接实现。 事实上,其它同步技术的实现常常依赖于原子操作。

Go对原子操作的支持

Go 语言的sync/atomic包提供了对原子操作的支持,用于同步访问整数和指针。

  • Go语言提供的原子操作都是非入侵式的。
  • 这些函数提供的原子操作共有五种:增减、比较并交换、载入、存储、交换。
  • 原子操作支持的类型类型包括int32、int64、uint32、uint64、uintptr、unsafe.Pointer。

下面的示例演示如何使用AddInt32函数对int32值执行添加原子操作。 在这个例子中,main goroutine创建了1000个的并发goroutine。 每个新创建的goroutine将整数n加1。

package mainimport ("fmt""sync""sync/atomic"
)func main() {var n int32var wg sync.WaitGroupfor i := 0; i < 1000; i++ {wg.Add(1)go func() {atomic.AddInt32(&n, 1)wg.Done()}()}wg.Wait()fmt.Println(atomic.LoadInt32(&n)) // output:1000
}

上面的例子里你们可以自己试验一下,如果我们不使用atomic.AddInt32(&n, 1)而是简单的对变量n进行自增的话得到结果并不是我们预期的1000,这就是我们在文章《Go并发编程里的数据竞争以及解决之道》里提到过的数据竞争问题,原子操作可确保这些goroutine之间不存在数据竞争。

原子操作中的比较并交换简称CAS(Compare And Swap),在sync/atomic包中,这类原子操作由名称以CompareAndSwap为前缀的若干个函数提供

func CompareAndSwapInt32(addr *int32, old, new int32) (swapped bool)
func CompareAndSwapPointer(addr *unsafe.Pointer,old, new unsafe.Pointer) (swapped bool)
......

调用函数后,CompareAndSwap函数会先判断参数addr指向的操作值与参数old的值是否相等,仅当此判断得到的结果是true之后,才会用参数new代表的新值替换掉原先的旧值,否则操作就会被忽略。

我们使用的mutex互斥锁类似悲观锁,总是假设会有并发的操作要修改被操作的值,所以使用锁将相关操作放入临界区中加以保护。而使用CAS操作的做法趋于乐观锁,总是假设被操作值未曾被改变(即与旧值相等),并一旦确认这个假设的真实性就立即进行值替换。在被操作值被频繁变更的情况下,CAS操作并不那么容易成功所以需要不断进行尝试,直到成功为止。

package mainimport ("fmt""sync/atomic"
)var value int32 = 1func main()  {fmt.Println("======old value=======")fmt.Println(value)fmt.Println("======New value=======")fmt.Println(value)}//不断地尝试原子地更新value的值,直到操作成功为止
func addValue(delta int32){for {v := valueif atomic.CompareAndSwapInt32(&value, v, (v + delta)){break}}
}

上面的比较并交换案例中 v:= value为变量v赋值,但要注意,在进行读取value的操作的过程中,其他对此值的读写操作是可以被同时进行的,那么这个读操作很可能会读取到一个只被修改了一半的数据。所以 我们要使用sync/atomic代码包中为我们提供的以Load为前缀的函数,来避免这样的糟糕事情发生。

竞争条件是由于异步的访问共享资源,并试图同时读写该资源而导致的,使用互斥锁和通道的思路都是在线程获得到访问权后阻塞其他线程对共享内存的访问,而使用原子操作解决数据竞争问题则是利用了其不可被打断的特性。

关于atomic包更详细的使用介绍可以访问官方的sync/atomic 中文文档

原子操作与互斥锁的区别

互斥锁是一种数据结构,使你可以执行一系列互斥操作。而原子操作是互斥的单个操作,这意味着没有其他线程可以打断它。那么就Go语言里atomic包里的原子操作和sync包提供的同步锁有什么不同呢?

首先atomic操作的优势是更轻量,比如CAS可以在不形成临界区和创建互斥量的情况下完成并发安全的值替换操作。这可以大大的减少同步对程序性能的损耗。

原子操作也有劣势。还是以CAS操作为例,使用CAS操作的做法趋于乐观,总是假设被操作值未曾被改变(即与旧值相等),并一旦确认这个假设的真实性就立即进行值替换,那么在被操作值被频繁变更的情况下,CAS操作并不那么容易成功。而使用互斥锁的做法则趋于悲观,我们总假设会有并发的操作要修改被操作的值,并使用锁将相关操作放入临界区中加以保护。

所以总结下来原子操作与互斥锁的区别有:

  • 互斥锁是一种数据结构,用来让一个线程执行程序的关键部分,完成互斥的多个操作。
  • 原子操作是针对某个值的单个互斥操作。
  • 可以把互斥锁理解为悲观锁,共享资源每次只给一个线程使用,其它线程阻塞,用完后再把资源转让给其它线程。

atomic包提供了底层的原子性内存原语,这对于同步算法的实现很有用。这些函数一定要非常小心地使用,使用不当反而会增加系统资源的开销,对于应用层来说,最好使用通道或sync包中提供的功能来完成同步操作。

针对atomic包的观点在Google的邮件组里也有很多讨论,其中一个结论解释是:

应避免使用该包装。或者,阅读C ++ 11标准的“原子操作”一章;如果您了解如何在C ++中安全地使用这些操作,那么你才能有安全地使用Go的sync/atomic包的能力。

推荐阅读

并发题的解题思路和Go语言调度器

Go语言的原子操作和互斥锁的区别相关推荐

  1. 原子操作和互斥锁的区别

    这个系列的文章里介绍了很多并发编程里经常用到的技术,除了Context.计时器.互斥锁还有通道外还有一种技术--原子操作在一些同步算法中会被用到.今天的文章里我们会简单了解一下Go语言里对原子操作的支 ...

  2. 什么是自旋锁+自旋锁和互斥锁的区别

    文章目录 本文链接 什么是自旋锁 参考链接 自旋锁和互斥锁的区别 参考链接 本文链接 击打 什么是自旋锁 多线程对共享资源访问, 为防止并发引起的相关问题, 常引入锁的机制来处理并发问题.   获取到 ...

  3. 原子操作和互斥量的区别

    原子操作和互斥量的区别 原子操作和互斥锁都是并发编程中常见的技术. 原子操作 原子操作就是操作过程中不能被中断的过程,在针对某个值得原子操作,在被进行的过程中CPU绝对不会再进行其他的针对该值的操作. ...

  4. 自旋锁与互斥锁的区别,信号量和互斥锁的区别

    文章目录 自旋锁与互斥锁的区别 信号量和互斥锁的区别 什么是信号量 什么是互斥锁 主要区别 生产者-消费者问题 使用mutex 使用信号量 信号量的优点 mutex的优点 信号量的缺点 mutex的缺 ...

  5. 学习笔记(26):Python网络编程并发编程-GIL与自定义互斥锁的区别

    立即学习:https://edu.csdn.net/course/play/24458/296443?utm_source=blogtoedu 1.GIL的基本概念 答:GIL本质上就是一把锁,只是他 ...

  6. 自旋锁和互斥锁的区别

    自旋锁和互斥锁的区别 POSIX threads(简称Pthreads)是在多核平台上进行并行编程的一套API.线程同步是并行编程中非常重要的通讯手段,其中最典型的应用就是用 Pthreads提供的锁 ...

  7. 自旋锁与互斥锁的区别

    自旋锁和互斥锁的区别 POSIX threads(简称Pthreads)是在多核平台上进行并行编程的一套API.线程同步是并行编程中非常重要的通讯手段,其中最典型的应用就是用 Pthreads提供的锁 ...

  8. 原子操作、互斥锁、读写锁

    原子操作 package mainimport ("fmt""sync""sync/atomic" //原子操作,比读写锁和互斥锁都要快,原 ...

  9. 自旋锁和互斥锁的区别 java中lock Syntronized区别

    转载自:http://blog.csdn.net/susidian/article/details/51068858 自旋锁(Spin lock) 自旋锁与互斥锁有点类似,只是自旋锁不会引起调用者睡眠 ...

最新文章

  1. 解决win下安装wordcloud出错问题
  2. mysql主从同步配置超详细_MySQL主从同步配置
  3. jQuery Validate 提交表单验证失败扩展方法
  4. Hive的基本操作-创建内部表
  5. 【物理/数学】—— 概念的理解 moment、momentum
  6. .NET 6 攻略大全(二)
  7. 04-树7 二叉搜索树的操作集 (30 分)
  8. 7-176 求n以内最大的k个素数以及它们的和 (20 分)
  9. 硬件电路基础知识(30)---RS232、RS485、RS422、RJ45接口的区别
  10. code函数oracle列子,Oracle Pivot函数语法详解及应用实例
  11. flowable 控制台打印 sql 语句
  12. js获取url参数值的几种方式
  13. 【C++】算法集锦(11):敏感词过滤算法(DFA)
  14. 【无标题】2022电工(技师)操作证考试题及在线模拟考试
  15. 电商数据库核心表设计
  16. 关于php网络爬虫phpspider
  17. c语言课程设计作业个人所得税计算,个税计算器2018-C语言编程个人所得税计算公式...
  18. 《缠中说禅108课》108:何谓底部?从月线看中期走势演化
  19. LATEX强制放置表格图片在固定的位置
  20. 网络世界强权崛起,全球竞相取经

热门文章

  1. url传参(中文乱码)值得注意的地方
  2. 1094:零起点学算法01——第一个程序Hello World!
  3. [Leetcode]-- Valid Number
  4. jsp session
  5. Java 工程师成神之路 | 2019正式版
  6. mod_rewrite
  7. Vert.x MySQLClient体验
  8. SDS趋势之二:对象存储将替代文件存储
  9. Java Protected 解读
  10. SQL语句中=null和is null