#golang #atomic


互斥锁跟原子操作的区别

在并发编程里,Go语言sync包里的同步原语Mutex是我们经常用来保证并发安全的,但是他跟atomic包在使用目的和底层实现上都不一样:

使用目的

互斥锁是用来保护一段逻辑,原子操作用于对一个变量的更新保护。

底层实现

Mutex由操作系统的调度器实现,而atomic包中的原子操作则由底层硬件指令直接提供支持,这些指令在执行的过程中是不允许中断的,因此原子操作可以在lock-free的情况下保证并发安全,并且它的性能也能做到随CPU个数的增多而线性扩展。

对于一个变量更新的保护,原子操作通常会更有效率,并且更能利用计算机多核的优势。

性能测试对比

互斥锁性能测试

使用sync包下面互斥锁的多线程加法操作

func syncAdd(param int64) int64 {
	var wg sync.WaitGroup
	lock := sync.Mutex{}
	for i := 0; i < 10; i++ {
		wg.Add(1)
		go func() {
			for i := 0; i < 1000000; i++ {
				lock.Lock()
				param++
				lock.Unlock()
			}
			wg.Done()
		}()
	}
	wg.Wait()
	return param
}

Benchmark测试方法

func BenchmarkSync(b *testing.B) {
	for i := 0; i < b.N; i++ {
		flag := int64(0)
		res := syncAdd(flag)
		if res != 10000000 {
			b.Errorf("calculate result err: %d\n", res)
		}
	}
}

测试结果:

根据运行环境和硬件性能会有所不同,这里是在相同环境下的对比

  • 第一次
goos: darwin
goarch: arm64
pkg: wire/atomic_test
BenchmarkSync
BenchmarkSync-8   	       2	 862741542 ns/op
PASS
  • 第二次
goos: darwin
goarch: arm64
pkg: wire/atomic_test
BenchmarkSync
BenchmarkSync-8   	       2	 875432729 ns/op
PASS
  • 第三次
goos: darwin
goarch: arm64
pkg: wire/atomic_test
BenchmarkSync
BenchmarkSync-8   	       2	 836373292 ns/op
PASS

三次取平均:(862741542 + 875432729 + 836373292) / 3 = 858182521 ns/op

原子操作性能测试

使用atomic包下面原子操作的多线程加法操作

func atomicAdd(param int64) int64 {
	var wg sync.WaitGroup
	for i := 0; i < 10; i++ {
		wg.Add(1)
		go func() {
			for i := 0; i < 1000000; i++ {
				atomic.AddInt64(&param, 1)
			}
			wg.Done()
		}()
	}
	wg.Wait()
	return param
}

Benchmark测试方法

func BenchmarkAtomic(b *testing.B) {
	for i := 0; i < b.N; i++ {
		flag := int64(0)
		res := atomicAdd(flag)
		if res != 10000000 {
			b.Errorf("calculate result err: %d\n", res)
		}
	}
}

测试结果:

根据运行环境和硬件性能会有所不同,这里是在相同环境下的对比

  • 第一次
goos: darwin
goarch: arm64
pkg: wire/atomic_test
BenchmarkAtomic
BenchmarkAtomic-8   	       4	 359013958 ns/op
PASS
  • 第二次
goos: darwin
goarch: arm64
pkg: wire/atomic_test
BenchmarkAtomic
BenchmarkAtomic-8   	       3	 359734514 ns/op
PASS
  • 第三次
goos: darwin
goarch: arm64
pkg: wire/atomic_test
BenchmarkAtomic
BenchmarkAtomic-8   	       4	 359007542 ns/op
PASS

三次取平均:(359013958 + 359734514 + 359007542) / 3 = 359252004 ns/op

测试结果对比

根据测试结果数据使用互斥锁做累加每次循环耗时858182521 ns,而使用原子操作做累加每次耗时359252004 ns

这也印证了之前说过的:互斥锁适用于来保护一段逻辑,原子操作适用于于对一个变量的更新保护。

原理浅析

参考: 互斥锁跟原子操作的区别-底层实现

参考链接