📚 原创系列: “Go语言学习系列”
🔄 转载说明: 本文最初发布于"Gopher部落"微信公众号,经原作者授权转载。
🔗 关注原创: 欢迎扫描文末二维码,关注"Gopher部落"微信公众号获取第一手Go技术文章。
📑 Go语言学习系列导航
🚀 第三阶段:进阶篇本文是【Go语言学习系列】的第33篇,当前位于第三阶段(进阶篇)
- 并发编程(一):goroutine基础
- 并发编程(二):channel基础
- 并发编程(三):select语句
- 并发编程(四):sync包
- 并发编程(五):并发模式
- 并发编程(六):原子操作与内存模型 👈 当前位置
- 数据库编程(一):SQL接口
- 数据库编程(二):ORM技术
- Web开发(一):路由与中间件
- Web开发(二):模板与静态资源
- Web开发(三):API开发
- Web开发(四):认证与授权
- Web开发(五):WebSocket
- 微服务(一):基础概念
- 微服务(二):gRPC入门
- 日志与监控
- 第三阶段项目实战:微服务聊天应用
📖 文章导读
在本文中,您将了解:
- 什么是原子操作,为什么它在并发编程中如此重要
- sync/atomic包提供的基本操作和新的原子类型
- Go内存模型中的happens-before关系及其重要性
- 内存重排和其对并发程序的影响
- 如何避免常见的并发陷阱和优化并发性能
- 使用原子操作实现高效的并发数据结构

并发编程(六):原子操作与内存模型
1. 什么是原子操作
在并发编程中,**原子操作(Atomic Operations)**是指不会被线程调度机制中断的操作,这类操作一旦开始,就会在CPU的一个时钟周期内执行完毕。原子操作在执行过程中不会被其他线程或进程打断,因此可以保证操作的完整性和一致性。
原子操作的主要特点:
- 不可分割性:原子操作是最小的执行单位,它要么完全执行,要么完全不执行
- 并发安全:多个线程同时对同一变量进行原子操作不会导致数据竞争
- 可见性保证:原子操作还确保操作的结果对其他线程可见
在Go语言中,原子操作是通过sync/atomic包实现的,它提供了低级别的原子内存原语,用于实现同步算法。
2. 为什么需要原子操作
我们知道在并发编程中可以使用互斥锁(Mutex)来保护共享资源,那为什么还需要原子操作呢?以下是几个主要原因:
2.1 性能考虑
相比互斥锁,原子操作通常有更好的性能,因为它们:
- 不需要操作系统的调度器参与
- 直接在硬件层面实现
- 避免了上下文切换的开销
请看这个简单的性能对比:
package main
import (
"sync"
"sync/atomic"
"testing"
)
func BenchmarkMutex(b *testing.B) {
var count int64
var mu sync.Mutex
for i := 0; i < b.N; i++ {
mu.Lock()
count++
mu.Unlock()
}
}
func BenchmarkAtomic(b *testing.B) {
var count int64
for i := 0; i < b.N; i++ {
atomic.AddInt64(&count, 1)
}
}
在绝大多数情况下,原子操作的性能会比互斥锁高出数倍甚至数十倍。
2.2 适用于简单操作
原子操作非常适合于对单个变量进行简单操作,如:
- 递增/递减计数器
- 交换值
- 比较并交换(CAS)操作
2.3 避免死锁风险
使用原子操作可以避免某些死锁场景,因为它们不会阻塞线程。
3. sync/atomic包的基本操作
Go语言的sync/atomic包提供了几类基本操作:
3.1 加载和存储操作
// 读取操作 - 原子地加载值
func Load(addr *T) T
// 存储操作 - 原子地存储值
func Store(addr *T, val T)
这些函数确保在多个goroutine之间读写变量时不会导致数据竞争。
示例:原子加载和存储
package main
import (
"fmt"
"sync/atomic"
"time"
)
func main() {
var value int32 = 0
// 并发写入
go func() {
for i := 0; i < 5; i++ {
atomic.StoreInt32(&value, int32(i))
time.Sleep(100 * time.Millisecond)
}
}()
// 并发读取
go func() {
for i := 0; i < 10; i++ {
val := atomic.LoadInt32(&value)
fmt.Println("Value:", val)
time.Sleep(50 * time.Millisecond)
}
}()
time.Sleep(1 * time.Second)
}
3.2 增减操作
// 增加操作 - 原子地将delta加到addr并返回新值
func Add(addr *T, delta T) T
// 减一操作 - 原子地将addr减1并返回新值
func AddInt64(addr *int64, delta int64) (new int64)
示例:原子计数器
package main
import (
"fmt"
"sync"
"sync/atomic"
)
func main() {
var counter int64 = 0
var wg sync.WaitGroup
// 启动100个goroutine,每个对计数器增加100
for i := 0; i < 100; i++ {
wg.Add(1)
go func() {
defer wg.Done()
for j := 0; j < 100; j++ {
atomic.AddInt64(&counter, 1)
}
}()
}
wg.Wait()
fmt.Println("Final Counter:", counter) // 期望输出: 10000
}
3.3 交换操作
// 原子地将新值存入addr并返回旧值
func Swap(addr *T, new T) (old T)
示例:原子交换值
package main
import (
"fmt"
"sync/atomic"
)
func main() {
var value int32 = 100
// 原子地将值从100交换成200,并获取旧值
oldValue := atomic.SwapInt32(&value, 200)
fmt.Println("Old value:", oldValue) // 输出: 100
fmt.Println("New value:", value) // 输出: 200
}
3.4 比较并交换(CAS)操作
// 如果addr的值等于old,则将new写入addr
func CompareAndSwap(addr *T, old, new T) (swapped bool)
CAS操作是实现无锁数据结构的基础,它允许我们在不使用互斥锁的情况下安全地修改值。
示例:使用CAS实现自旋锁
package main
import (
"fmt"
"sync"
"sync/atomic"
)
// 简单的自旋锁实现
type SpinLock struct {
locked int32
}
// 尝试获取锁
func (l *SpinLock) Lock() {
// 不断尝试将locked从0设为1
for !atomic.CompareAndSwapInt32(&l.locked, 0, 1) {
// 自旋等待
}
}
// 释放锁
func (l *SpinLock

最低0.47元/天 解锁文章
1786

被折叠的 条评论
为什么被折叠?



