go 并发模型 之 sync -- atomic (五)


前言

Go 提供内存同步机制,例如通道或互斥锁,这些机制有助于解决不同的问题。在共享内存的情况下,互斥锁保护内存不受数据竞争的影响。然而,尽管存在两个互斥锁,Go 还是通过原子包提供原子内存原语来提高性能。在深入研究解决方案之前,让我们先回顾一下数据竞赛。

当两个或多个 goroutine 并发访问相同的内存位置并且其中至少有一个正在写入时,可能会发生数据竞争。虽然映射有一个本地机制来防止数据竞争,但是一个简单的结构没有任何数据竞争,这使得它容易受到数据竞争的攻击。

一 atomic

 atomic 官方描述

Package atomic provides low-level atomic memory primitives useful for implementing synchronization algorithms.

包原子提供了用于实现同步算法的低级原子内存原语。

These functions require great care to be used correctly. Except for special, low-level applications, synchronization is better done with channels or the facilities of the sync package. Share memory by communicating; don't communicate by sharing memory.

正确使用这些功能需要非常小心。除了特殊的底层应用程序,同步最好是通过通道或同步包的设施来完成。通过交流来共享内存,而不是通过共享内存来交流。

1.方法展示

  add 类型的  在 addr 的基础上 加或者减 delta

func AddInt32(addr *int32, delta int32) (new int32)
func AddInt64(addr *int64, delta int64) (new int64)
func AddUint32(addr *uint32, delta uint32) (new uint32)
func AddUint64(addr *uint64, delta uint64) (new uint64)
func AddUintptr(addr *uintptr, delta uintptr) (new uintptr)

CompareAndSwapXXX 类型的  CAS 操作 会先比较传入的地址的值是否是 old,如果是的话就尝试赋新值,如果不是的话就直接返回 false,返回 true 时表示赋值成功

func CompareAndSwapInt32(addr *int32, old, new int32) (swapped bool)
func CompareAndSwapInt64(addr *int64, old, new int64) (swapped bool)
func CompareAndSwapPointer(addr *unsafe.Pointer, old, new unsafe.Pointer) (swapped bool)
func CompareAndSwapUint32(addr *uint32, old, new uint32) (swapped bool)
func CompareAndSwapUint64(addr *uint64, old, new uint64) (swapped bool)
func CompareAndSwapUintptr(addr *uintptr, old, new uintptr) (swapped bool)

LoadIntxxx 从某个地址中取值

func LoadInt32(addr *int32) (val int32)
func LoadInt64(addr *int64) (val int64)
func LoadPointer(addr *unsafe.Pointer) (val unsafe.Pointer)
func LoadUint32(addr *uint32) (val uint32)
func LoadUint64(addr *uint64) (val uint64)
func LoadUintptr(addr *uintptr) (val uintptr)

StoreIntXXX 给某个地址赋值

func StoreInt32(addr *int32, val int32)
func StoreInt64(addr *int64, val int64)
func StorePointer(addr *unsafe.Pointer, val unsafe.Pointer)
func StoreUint32(addr *uint32, val uint32)
func StoreUint64(addr *uint64, val uint64)
func StoreUintptr(addr *uintptr, val uintptr)

SwapIntXXX 交换两个值返回旧的值

func SwapInt32(addr *int32, new int32) (old int32)
func SwapInt64(addr *int64, new int64) (old int64)
func SwapPointer(addr *unsafe.Pointer, new unsafe.Pointer) (old unsafe.Pointer)
func SwapUint32(addr *uint32, new uint32) (old uint32)
func SwapUint64(addr *uint64, new uint64) (old uint64)
func SwapUintptr(addr *uintptr, new uintptr) (old uintptr)

任意类型的取值和赋值

type Value
func (v *Value) Load() (x interface{})
func (v *Value) Store(x interface{})

二 案例

读多写少的状态下Mutex 和 Atomic.value 的对比

func BenchmarkRWMutex(t *testing.B)  {
	var l sync.RWMutex

	var cfg *Config

	go func() {
		i := 0
		for  {
			i++
			l.Lock()
			cfg = &Config{
				data: []int{i, i + 1, i + 2, i + 3, i + 4, i + 5},
			}
			l.Unlock()
		}
	}()

	var wg sync.WaitGroup

	for n := 0; n < 4; n++ {
		wg.Add(1)
		go func() {
			for n := 0; n < t.N; n++ {
				l.RLock()
					cfg.T()
				l.RUnlock()
			}
			wg.Done()
		}()
	}
	wg.Wait()

}


func BenchmarkAtomic(t *testing.B)  {
	var v atomic.Value
	v.Store(&Config{})
	go func() {
		i := 0
		for  {
			i++
			cfg := &Config{
				data: []int{i,i+1,i+2,i+3,i+4,i+5},
			}
			v.Store(cfg)
		}
	}()

	var wg sync.WaitGroup

	for n := 0; n < 4; n++ {
		wg.Add(1)
		go func() {
			for n := 0; n < t.N; n++ {
				cfg := v.Load().(*Config)
				cfg.T()
				//fmt.Println(cfg.data)
			}
			wg.Done()
		}()
	}
	wg.Wait()

}

结果如下

goos: windows
goarch: amd64
BenchmarkRWMutex-6       3069406               666 ns/op
BenchmarkAtomic-6       625246248                2.25 ns/op
PASS
ok      command-line-arguments  5.323s
 

同样的数据  Mutex 版本的需要 666 纳秒

而 Atomic.value 的版本需要 2.25纳秒 并且没有 不会出现 data race

Mutex 为什么这么慢呢?

Mutex 相对更重。
因为涉及到更多的 goroutine 之间的上下文切换
pack blocking goroutine,以及唤醒 goroutine。

Atomic.value 为什么这么快呢?

使用了Copy-On-Write 写时复制的思想 

写操作时候复制全量老数据到一个新的对象中,携带上 本次新写的数据, 之后利用原子替换(atomic.Value),更新调用者的变量。来完成无锁访问共享数据

文献

atomic package - sync/atomic - pkg.go.dev 官方文档

https://medium.com/a-journey-with-go/go-how-to-reduce-lock-contention-with-the-atomic-package-ba3b2664b549


总结

这是一个底层库 在实际业务当中 还是使用channel 为好。如果场景需要请小心使用!!!

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值