《7天学会Go并发编程》第7天 go语言并发编程Atomic原子实战操作含ABA问题

😯为什么在最后一篇文章才会讲解Atmoic操作,是因为atomic操作在业务开发中应用较少,故将其放在最后。但是论重要性来说,atomic操作是最核心最关键一环。

  系列文章目录

《7天学会Go并发编程》第一天 go并发实战初窥门径_大锤爱编程的博客-CSDN博客

《7天学会Go并发编程》の第二天 写一个简单的Go并发程序以及Goroutine的使用_大锤爱编程的博客-CSDN博客

《7天学会Go并发编程》第三天 go的Mutex使用_大锤爱编程的博客-CSDN博客

《7天学会Go并发编程》第四天 sync.Once和sync.WaitGroup学习应用实践_大锤爱编程的博客-CSDN博客

《7天学会Go并发编程》第五天 go语言Context的应用和实现 go实现超时判断_大锤爱编程的博客-CSDN博客

《7天学会Go并发编程》第六天 go语言Sync.cond的应用和实现 go实现多线程联合执行_大锤爱编程的博客-CSDN博客

《7天学会Go并发编程》第7天 go语言并发编程Atomic原子实战操作含ABA问题_大锤爱编程的博客-CSDN博客

目录

一、Atomic-原子操作简介

二、🍻go语言Atomic源码分析和实战操作

2.1🍺🍺🍺 Atomic 关键函数及其释义

2.1 🍭🍭🍭atomic实战

2.2 🍖🍖🍖atomic原子并发操作

2.3 🍟🍟🍟任意数据结构的原子操作

三、🍏🍏🍏总结



一、Atomic-原子操作简介

原子操作其实和原子没有什么关系,主要借用的是原子不可分割的概念来强调说明这个操作也不可以被分割。

🚗使用并发编程时需要解决的就是临界区(公共资源的修改)问题,抽象的数据操作行为是RAW,WAR,WAW。🥤🥤🥤体现在具体编码过程中,就是RW,在RW过程中,不会存在其他线程对这个临界区就是操作。就是这个原子操作一次只可以被一个线程来完成,不存在多个线程同时进行原子操作。

简单的概括就是:

使用原子操作时,就像过一个两头窄,中间宽的胡同。两头窄到只允许一个线程通过。

这样:

二、🍻go语言Atomic源码分析和实战操作

go语言Atmoic相关定义实现在主要在sync.Atomic包中,Atmoic包中提供低级原子内存原语

用于实现同步算法。

2.1🍺🍺🍺 Atomic 关键函数及其释义

    doc.go主要定义了SWAP(变量之间的交换操作)、CAS(变量比较交换操作)、ADD(原子变量加操作)、LOAD(原子变量的数据加载操作)、STORE(原子变量的存储操作)。

其中相对来说比较复杂的就是CAS操作:

比较并交换(compare and swap, CAS),用于在多线程编程中实现不被打断的数据交换操作,从而避免多线程同时改写某一数据时由于执行顺序不确定性以及中断的不可预知性产生的数据不一致问题。 该操作通过将内存中的值与指定数据进行比较,当数值一样时将内存中的数据替换为新的值。

源码释义:

旧值和内存中的地址进行比较,如果旧值和内存地址比较相等的说,则说明内存地址并未修改,可以使用新值写入地址。

入参定义: 

addr *uint64:内存存储数据的地址

 old:旧值

 new uint64: 新值

返参定义:

swapped (TRUE:交换成功,FALSE:交换失败)

// CompareAndSwapUint64 executes the compare-and-swap operation for a uint64 value.
func CompareAndSwapUint64(addr *uint64, old, new uint64) (swapped bool)

CAS上的ABA问题:

ABA问题是CAS常见的一种问题,可基本表述为:

  1. 进程P1读取了一个数值A
  2. P1被挂起(时间片耗尽、中断等),进程P2开始执行
  3. P2修改数值A为数值B,然后又修改回A
  4. P1被唤醒,比较后发现数值A没有变化,程序继续执行

逻辑上CAS是是可以解决多线程数据交换的冲突问题,但是如果内存中存储的值被其他线程修改过了,且修改的值和原有旧值一样。其具体危害详见https://zh.wikipedia.org/zh-cn/%E6%AF%94%E8%BE%83%E5%B9%B6%E4%BA%A4%E6%8D%A2https://zh.wikipedia.org/zh-cn/%E6%AF%94%E8%BE%83%E5%B9%B6%E4%BA%A4%E6%8D%A2

完整源码:

package atomic

import (
	"unsafe"
)

// BUG(rsc): On 386, the 64-bit functions use instructions unavailable before the Pentium MMX.
//
// On non-Linux ARM, the 64-bit functions use instructions unavailable before the ARMv6k core.
//
// On ARM, 386, and 32-bit MIPS, it is the caller's responsibility
// to arrange for 64-bit alignment of 64-bit words accessed atomically.
// The first word in a variable or in an allocated struct, array, or slice can
// be relied upon to be 64-bit aligned.

// SwapInt32 atomically stores new into *addr and returns the previous *addr value.
func SwapInt32(addr *int32, new int32) (old int32)

// SwapInt64 atomically stores new into *addr and returns the previous *addr value.
func SwapInt64(addr *int64, new int64) (old int64)

// SwapUint32 atomically stores new into *addr and returns the previous *addr value.
func SwapUint32(addr *uint32, new uint32) (old uint32)

// SwapUint64 atomically stores new into *addr and returns the previous *addr value.
func SwapUint64(addr *uint64, new uint64) (old uint64)

// SwapUintptr atomically stores new into *addr and returns the previous *addr value.
func SwapUintptr(addr *uintptr, new uintptr) (old uintptr)

// SwapPointer atomically stores new into *addr and returns the previous *addr value.
func SwapPointer(addr *unsafe.Pointer, new unsafe.Pointer) (old unsafe.Pointer)

// CompareAndSwapInt32 executes the compare-and-swap operation for an int32 value.
func CompareAndSwapInt32(addr *int32, old, new int32) (swapped bool)

// CompareAndSwapInt64 executes the compare-and-swap operation for an int64 value.
func CompareAndSwapInt64(addr *int64, old, new int64) (swapped bool)

// CompareAndSwapUint32 executes the compare-and-swap operation for a uint32 value.
func CompareAndSwapUint32(addr *uint32, old, new uint32) (swapped bool)

// CompareAndSwapUint64 executes the compare-and-swap operation for a uint64 value.
func CompareAndSwapUint64(addr *uint64, old, new uint64) (swapped bool)

// CompareAndSwapUintptr executes the compare-and-swap operation for a uintptr value.
func CompareAndSwapUintptr(addr *uintptr, old, new uintptr) (swapped bool)

// CompareAndSwapPointer executes the compare-and-swap operation for a unsafe.Pointer value.
func CompareAndSwapPointer(addr *unsafe.Pointer, old, new unsafe.Pointer) (swapped bool)

// AddInt32 atomically adds delta to *addr and returns the new value.
func AddInt32(addr *int32, delta int32) (new int32)

// AddUint32 atomically adds delta to *addr and returns the new value.
// To subtract a signed positive constant value c from x, do AddUint32(&x, ^uint32(c-1)).
// In particular, to decrement x, do AddUint32(&x, ^uint32(0)).
func AddUint32(addr *uint32, delta uint32) (new uint32)

// AddInt64 atomically adds delta to *addr and returns the new value.
func AddInt64(addr *int64, delta int64) (new int64)

// AddUint64 atomically adds delta to *addr and returns the new value.
// To subtract a signed positive constant value c from x, do AddUint64(&x, ^uint64(c-1)).
// In particular, to decrement x, do AddUint64(&x, ^uint64(0)).
func AddUint64(addr *uint64, delta uint64) (new uint64)

// AddUintptr atomically adds delta to *addr and returns the new value.
func AddUintptr(addr *uintptr, delta uintptr) (new uintptr)

// LoadInt32 atomically loads *addr.
func LoadInt32(addr *int32) (val int32)

// LoadInt64 atomically loads *addr.
func LoadInt64(addr *int64) (val int64)

// LoadUint32 atomically loads *addr.
func LoadUint32(addr *uint32) (val uint32)

// LoadUint64 atomically loads *addr.
func LoadUint64(addr *uint64) (val uint64)

// LoadUintptr atomically loads *addr.
func LoadUintptr(addr *uintptr) (val uintptr)

// LoadPointer atomically loads *addr.
func LoadPointer(addr *unsafe.Pointer) (val unsafe.Pointer)

// StoreInt32 atomically stores val into *addr.
func StoreInt32(addr *int32, val int32)

// StoreInt64 atomically stores val into *addr.
func StoreInt64(addr *int64, val int64)

// StoreUint32 atomically stores val into *addr.
func StoreUint32(addr *uint32, val uint32)

// StoreUint64 atomically stores val into *addr.
func StoreUint64(addr *uint64, val uint64)

// StoreUintptr atomically stores val into *addr.
func StoreUintptr(addr *uintptr, val uintptr)

// StorePointer atomically stores val into *addr.
func StorePointer(addr *unsafe.Pointer, val unsafe.Pointer)

2.1 🍭🍭🍭atomic实战

atomic基本使用来说比较简单,就是使用atmoc.func来完成对应的增加,赋值操作。

package main

import (
	"fmt"
	"sync/atomic"
)

func main() {
	var operationNums = int32(10)
	atomic.AddInt32(&operationNums,12)
	fmt.Printf("修改后的数值:%d\n",atomic.LoadInt32(&operationNums))
}

2.2 🍖🍖🍖atomic原子并发操作

释义:并发修改变量的值,并发阅读变量的值。

package main

import (
	"fmt"
	"sync"
	"sync/atomic"
	"time"
)

func main() {
	var operationNums = int32(10)
	group := sync.WaitGroup{}
	group.Add(20)
	for i := 0; i < 10; i++ {
		go func() {
			defer group.Done()
			time.Sleep(200*time.Millisecond)
			atomic.AddInt32(&operationNums,1)

		}()
	}
	for i := 0; i < 10; i++ {
		go func(i int) {
			defer group.Done()
			time.Sleep(200*time.Millisecond)
			fmt.Printf("线程%d阅读修改后的数值:%d\n",i,atomic.LoadInt32(&operationNums))
		}(i)
	}
	group.Wait()
}

2.3 🍟🍟🍟任意数据结构的原子操作

通过var声明一个atomic.Value类型的变量。对这个变量进行相关的原子操作。

package main

import (
	"fmt"
	"sync"
	"sync/atomic"
	"time"
)

func main() {

	var box atomic.Value
	fmt.Println("Copy box to box2.")
	v1 := [...]int{1, 2, 3}
	fmt.Printf("Store %v to box.\n", v1)
	box.Store(v1)
	fmt.Printf("The value load from box is %v.\n", box.Load())
	fmt.Println()
}

  执行结果:毫无意外,又是正确执行。

Copy box to box2.
Store [1 2 3] to box.
The value load from box is [1 2 3].

三、🍏🍏🍏总结

        今天主要介绍了atomic的概念和简单使用,比较浅显。对于atomic应用感兴趣的同学,可以在go语言相关的并发源码包中探索。源码之下,了无秘密。加油加油!💪🏻⛽️~

🧧🧧🧧感谢诸位大佬的阅读,点个关注,收藏一下呗~

    

  • 10
    点赞
  • 11
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 10
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 10
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

大锤爱编程

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值