Golang中的同步工具原子操作详解

开发 前端
原子操作是指一系列操作要么全部执行成功,要么全部执行失败,不会有中间状态。

前面几篇文章介绍了Golang中互斥锁、读写锁、条件变量,虽然它们可以很好地协调对共享资源的访问,但并不能保证原子操作。

原子操作

原子操作是指一系列操作要么全部执行成功,要么全部执行失败,不会有中间状态。

锁无法保证原子性是因为:

  • 在锁保护的临界区代码执行期间,其他协程无法访问该代码段,但是它们可以访问其他资源,可能会导致原子操作失败;
  • 锁虽然能做到只让一个goroutine执行临界区代码,不被其他goroutine打扰,不过仍然可能被系统中断(因为goroutine都是统一被runtime调度的,runtime会频繁切换一个goroutine的运行状态)

可以看出原子操作的粒度更细,它对单个变量的访问进行了原子化保证,在操作完成之前会阻塞其他并发操作。能够保证原子性执行的只有原子操作,原子操作在执行过程中是不允许被中断的。在计算机底层,原子性是由CPU支持的,所以绝对有效。Golang中的原子操作是基于操作系统和CPU的,具体功能由标准库中的sync/atomic包提供。

sync/atomic

sync/atomic包提供的原子操作有Add、Load、Store、Swap和CompareAndSwap,这些函数支持的数据类型有int32、int64、uint32、uint64、uintptr和unsafe包中的Pointer,不过,没有提供针对unsafe.Pointer的Add方法。具体方法如下:

  • AddInt32/AddInt64/AddUint32/AddUint64/AddUintptr: 原子地将指定的值加到一个地址中的值上。
  • CompareAndSwapInt32/CompareAndSwapInt64/CompareAndSwapUint32/CompareAndSwapUint64/CompareAndSwapUintptr/CompareAndSwapPointer: 原子地比较一个指定类型地址中的值,如果该值和参数old匹配,就在那个地址处存储参数new。
  • SwapInt32/SwapInt64/SwapUint32/SwapUint64/SwapUintptr/SwapPointer: 原子地将值存储在指定地址处,并返回此地址的旧值。
  • LoadInt32/LoadInt64/LoadUint32/LoadUint64/LoadUintptr/LoadPointer: 原子地返回指定地址中的值。
  • StoreInt32/StoreInt64/StoreUint32/StoreUint64/StoreUintptr/StorePointer: 原子地将指定值存储到指定类型地址中。

此外,sync/atomic包还提供一个名称为Value的类型,可以被用来存储任意类型的值,结构如下:

type Value struct {
	v any
}

使用方法和示例

使用原子操作可以用于计算需要在多个goroutine之间共享的计数器。例如,计算在线用户数量、任务完成情况等等。

package main

import (
	"fmt"
	"sync/atomic"
)

func main() {
	var counter int64
	done := make(chan bool)

	for i := 0; i < 100; i++ {
		go func() {
			atomic.AddInt64(&counter, 1)
			done <- true
		}()
	}

	for i := 0; i < 100; i++ {
		<-done
	}

	fmt.Println(counter)
}

首先声明了一个int64类型的计数器变量counter,使用AddInt64原子操作对其进行递增。通过使用AddInt64,确保了每个goroutine对其值的修改操作都能够安全进行。

再看一个自旋锁的示例:

package main

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

func main() {
	sign := make(chan struct{}, 2)
	var num int32
	go func() {
		defer func() {
			sign <- struct{}{}
		}()
		for {
			// 定时增大num值
			time.Sleep(time.Millisecond * 500)
			newNum := atomic.AddInt32(&num, 2)
			fmt.Printf("num当前值为: %d\n", newNum)
			// 满足条件后退出
			if newNum == 10 {
				break
			}
		}
	}()
	go func() {
		// 定时检查num值,等于10则归零
		defer func() {
			sign <- struct{}{}
		}()
		for {
			if atomic.CompareAndSwapInt32(&num, 10, 0) {
				fmt.Println("已将num归零")
				break
			}
			time.Sleep(time.Millisecond * 500)
		}
	}()
	<-sign
	<-sign
}
责任编辑:姜华 来源: 今日头条
相关推荐

2023-06-26 08:28:35

Sync.CondGolang

2023-06-06 08:28:58

Sync.OnceGolang

2023-06-05 09:23:00

Golang同步工具

2021-09-22 12:56:19

编程技能Golang

2023-05-19 07:51:15

ChannelGolang

2023-08-08 14:51:29

2014-01-09 09:45:41

原子飞原子

2010-05-19 10:22:07

2023-07-05 08:38:48

GolangGo语言

2021-06-29 10:07:24

Javalong原子操作

2023-08-03 08:48:07

Golang接口

2023-03-30 07:52:03

Golang接口

2023-11-27 15:02:37

BytesGolang

2023-05-29 09:25:38

GolangSelect

2023-08-31 09:28:12

Golang可导出函数

2023-10-23 12:35:36

Golang追加操作

2010-02-26 14:29:32

Python 工具

2023-10-31 09:10:39

2023-06-09 08:16:09

GolangStruct Tag

2023-10-18 08:22:38

BufioGolang
点赞
收藏

51CTO技术栈公众号