Go atomic

Go atomic

一、介绍

atomic 是 Go 标准库中用于实现原子操作 的包,核心解决多 goroutine 并发下的变量竞态问题,比互斥锁(sync.Mutex)更轻量、性能更高。

二、原子操作

2.1 介绍

原子操作是指不可被中断的操作:要么完全执行完毕,要么完全不执行,中间不会被其他goroutine抢占。

2.2 底层原理

atomic.AddInt32(&x, 1) 是一步完成的,底层依赖 CPU 指令(如 LOCK XADD),通过锁定内存总线/ 缓存行,确保同一时刻只有一个CPU核心操作该内存地址,保证原子性,无竞态风险

三、适用场景

  • 轻量级的计数(eg 请求数、并发数统计)
  • 状态标记(eg 服务是否启动 任务是否完成)
  • 高性能的并发变量更新(替代互斥锁、减少上下文切换)

四、核心功能

atomic 主要操作基本数值类型int32/int64/uint32/uint64/uintptr)和指针类型 (Pointer)

ps :atomic 操作的对象是一个地址 ,需要传递地址

4.1 Add

对数值类型进行原子增减,给第一个参数地址中的值增加一个 delta 值

  • 变量必须是指针类型(因为要直接操作内存)
  • uint 类型加法:若要实现减法,需通过 atomic.AddUint32(&x, ^uint32(n-1))(利用补码),例如 atomic.AddUint32(&x, ^uint32(0)) 等价于 x--
go 复制代码
package main

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

func main() {
	var count int32 = 0 // 必须是int32/int64等原子支持的类型
	var wg sync.WaitGroup

	// 启动1000个goroutine,每个goroutine对count加1
	for i := 0; i < 1000; i++ {
		wg.Add(1)
		go func() {
			defer wg.Done()
			// 原子加1:第一个参数是变量指针,第二个是增量(负数表示减)
			atomic.AddInt32(&count, 1)
		}()
	}

	wg.Wait()
	fmt.Println("最终计数:", count) // 输出 1000(无竞态)
}

4.2 加载操作(Load)

原子读取变量值,保证读取到的是最新值(避免编译器 / CPU 缓存导致的可见性问题)

4.3 存储操作(Store)

原子写入变量值,保证写入的原子性和可见性(其他 goroutine 能立即看到最新值)

替代普通赋值 x = 0,避免并发下的写覆盖问题

go 复制代码
package main

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

var status int32 = 1

func getStatus() {
	fmt.Println(atomic.LoadInt32(&status))
}

func stop() {
	atomic.StoreInt32(&status, 0)
}
func main() {
	go getStatus()
	go stop()
	time.Sleep(time.Second)
}
结果可能是1也可能是0

4.4 比较并交换(CAS)

go 复制代码
func CompareAndSwapInt32(addr *int32, old, new int32) (swapped bool)

方法逻辑:

  • 检查 addr 指向的变量值是否等于 old
  • 若相等,将其更新为 new,返回 true
  • 若不相等,不做修改,返回 false

CAS 是乐观锁思想,无阻塞但可能重试;

Go 中 sync/atomic 的 CAS 是硬件级别的支持,性能远高于互斥锁

go 复制代码
package main

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

var status int32 = 1

func incr() {
	for {
		old := atomic.LoadInt32(&status)
		new := old + 1
		// CAS操作:只有old等于当前值时才更新
		if atomic.CompareAndSwapInt32(&status, old, new) {
			fmt.Println("status:", atomic.LoadInt32(&status))
			break
		}
	}
}

func main() {
	go incr()
	time.Sleep(time.Second)
}

4.5 交换操作(Swap)

无条件原子替换变量值,返回旧值,eg: 强制重置计数器

4.5 atomic.Value

支持`任意类型的原子读写

ps :

  • atomic.Value 存储的类型必须固定(比如第一次存 map,后续不能存 int)
  • 存储引用类型(如 map、slice)时,必须拷贝后修改,避免并发读写原对象
go 复制代码
package main

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

func main() {

	var date atomic.Value
	// 1. 存储数据
	date.Store(map[string]time.Time{"time1": time.Now()})

	var wg sync.WaitGroup
	for i := 0; i < 10; i++ {
		wg.Add(1)
		go func() {
			defer wg.Done()
			// 2. 加载数据
			oldMap := date.Load().(map[string]time.Time)
			// 3. 拷贝旧数据(避免修改原数据)
			newMap := make(map[string]time.Time)
			for k, v := range oldMap {
				newMap[k] = v
			}
			buf := bytes.NewBuffer([]byte{})
			buf.WriteString("time")
			buf.WriteString(strconv.Itoa(i))
			newMap[buf.String()] = time.Now()
			date.Store(newMap)
		}()
	}
	wg.Wait()
	fmt.Println(len(date.Load().(map[string]time.Time)))
	fmt.Println(date.Load().(map[string]time.Time))
	fmt.Println("main done")
}

五、与mutex比较

特性 atomic sync.Mutex
适用场景 单个变量的原子操作 (如计数、状态标记 复杂逻辑 / 多个变量的原子操作(比如转账(扣减 A 账户 + 增加 B 账户))
性能 极高(硬件指令级) 较低(有上下文切换开销)
使用复杂度 简单(仅支持基本操作) 灵活(支持任意逻辑)
竞态保护 仅保护变量读写 保护代码块
相关推荐
tedcloud1238 分钟前
God‘s Eye View 怎么搭建?在云服务器上部署一个实时 3D 地球可视化平台
linux·服务器·开发语言·后端·rust
深入云栈13 分钟前
Netty 4.2.x 源码深度解析 (十三):Epoll 传输 —— Linux 高性能 IO 的 Netty 实现
java·后端
Lyra_Infra18 分钟前
从一段错误 JSON 说起:Policy、Role 与 IAM
后端·json·aigc
Charlie_Byte18 分钟前
在用户目录安装多版本 JDK,并用环境变量切换
后端
Tairitsu_H27 分钟前
[C++] C++11 Lambda与包装器深度解析
开发语言·c++·c++11·lambda·包装器
傲世仙尊35 分钟前
从磁盘硬件到Ext文件系统-Linux磁盘级文件系统学习笔记
linux·运维·服务器·开发语言·c++
小羊没烦恼!1 小时前
Memory 记忆设计讨论:为什么 Agent Memory 不能只靠向量数据库?
java·开发语言·windows·算法·c#
黑马程序员毕设1 小时前
基于微信小程序的节目活动报名与管理系统设计与实现
java·开发语言·spring boot·后端·电脑
掘金者阿豪1 小时前
Windows 部署 Papra:建立私有文档库、标签管理,再配置固定公网访问
后端
Bs_MoneyMagnet1 小时前
基于springboot+vue的会议室预约管理系统的设计与实现 源码+文档
java·vue.js·spring boot·后端·spring·毕业设计·计算机毕业设计