Go语言精进之路读书笔记第36条——使用atomic包实现伸缩性更好的并发读取

atomic包提供了两大类原子操作接口:一类是针对整型变量的,包括有符号整型、无符号整型以及对应的指针类型;另一个类是针对自定义类型的。

atomic包十分适合一些对性能十分敏感、并发量较大且读多写少的场合。如果要对一个复杂的临界区数据进行同步,那么首选依旧是sync包中的原语。

36.1 atomic包与原子作#

atomic包是Go语言提供的原子操作原语相关的接口。

原子操作是相对于普通指令操作而言的,以一个整型变量自增的语句为例:

var a int
a++

a++这行语句需要以下3条普通机器指令来完成变量a的自增:

  • LOAD:将变量从内存加载到CPU寄存器
  • ADD:执行加法指令
  • STORE:将结果存储回原内存地址
    这3条普通指令在执行过程中是可中断的,而原子操作的指令是不可中断的,和事务类似。

原子操作由底层硬件直接提供,atomic包封装了CPU实现的部分原子操作指令,因此atomic包提供的原语更接近硬件底层,也更为低级,它常被用于实现更为高级的并发同步技术(如channel和sync包的同步原语)。

以atomic.SwapInt64函数在x86_64平台上的实现为例,它基本上就是对x86_64 CPU实现的原子操作指令XCHGQ的直接封装。

36.2 对共享整型变量的无锁读写#

  • 利用原子操作的无锁并发写的性能随着并发量增大几乎保持恒定
  • 利用原子操作的无锁并发读的性能随着并发量增加有持续提升的趋势,并且性能约为读锁的200倍
var n1 int64

func addSyncByAtomic(delta int64) int64 {
    return atomic.AddInt64(&n1, delta)
}

func readSyncByAtomic() int64 {
    return atomic.LoadInt64(&n1)
}

var n2 int64
var rwmu sync.RWMutex

func addSyncByRWMutex(delta int64) {
    rwmu.Lock()
    n2 += delta
    rwmu.Unlock()
}

func readSyncByRWMutex() int64 {
    var n int64
    rwmu.RLock()
    n = n2
    rwmu.RUnlock()
    return n
}

func BenchmarkAddSyncByAtomic(b *testing.B) {
    b.RunParallel(func(pb *testing.PB) {
        for pb.Next() {
            addSyncByAtomic(1)
        }
    })
}

func BenchmarkReadSyncByAtomic(b *testing.B) {
    b.RunParallel(func(pb *testing.PB) {
        for pb.Next() {
            readSyncByAtomic()
        }
    })
}

func BenchmarkAddSyncByRWMutex(b *testing.B) {
    b.RunParallel(func(pb *testing.PB) {
        for pb.Next() {
            addSyncByRWMutex(1)
        }
    })
}

func BenchmarkReadSyncByRWMutex(b *testing.B) {
    b.RunParallel(func(pb *testing.PB) {
        for pb.Next() {
            readSyncByRWMutex()
        }
    })
}

36.3 对共享自定义类型变量的无锁读写#

atomic通过Value类型的装拆箱操作实现了对任意自定义类型的原子操作(Load和Store),从而实现对共享自定义类型变量无锁读写的支持

  • 利用原子操作的无锁并发写的性能随着并发量增大而小幅下降
  • 利用原子操作的无锁并发读的性能随着并发量增加有持续提升的趋势,并且性能约为读锁的100倍
type Config struct {
    sync.RWMutex
    data string
}

func BenchmarkRWMutexSet(b *testing.B) {
    config := Config{}
    b.ReportAllocs()
    b.RunParallel(func(pb *testing.PB) {
        for pb.Next() {
            config.Lock()
            config.data = "hello"
            config.Unlock()
        }
    })
}

func BenchmarkRWMutexGet(b *testing.B) {
    config := Config{data: "hello"}
    b.ReportAllocs()
    b.RunParallel(func(pb *testing.PB) {
        for pb.Next() {
            config.RLock()
            _ = config.data
            config.RUnlock()
        }
    })
}

func BenchmarkAtomicSet(b *testing.B) {
    var config atomic.Value
    c := Config{data: "hello"}
    b.ReportAllocs()
    b.RunParallel(func(pb *testing.PB) {
        for pb.Next() {
            config.Store(c)
        }
    })
}

func BenchmarkAtomicGet(b *testing.B) {
    var config atomic.Value
    config.Store(Config{data: "hello"})
    b.ReportAllocs()
    b.RunParallel(func(pb *testing.PB) {
        for pb.Next() {
            _ = config.Load().(Config)
        }
    })
}

作者:brynchen

出处:https://www.cnblogs.com/brynchen/p/18032201

版权:本作品采用「署名-非商业性使用-相同方式共享 4.0 国际」许可协议进行许可。

posted @   brynchen  阅读(13)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 震惊!C++程序真的从main开始吗?99%的程序员都答错了
· 【硬核科普】Trae如何「偷看」你的代码?零基础破解AI编程运行原理
· 单元测试从入门到精通
· 上周热点回顾(3.3-3.9)
· winform 绘制太阳,地球,月球 运作规律
more_horiz
keyboard_arrow_up light_mode palette
选择主题
menu
点击右上角即可分享
微信分享提示