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

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

时间:2024-02-25 11:45:25浏览次数:25  
标签:读书笔记 testing 36 pb 并发 atomic func config

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)
        }
    })
}

标签:读书笔记,testing,36,pb,并发,atomic,func,config
From: https://www.cnblogs.com/brynchen/p/18032201

相关文章

  • Go语言精进之路读书笔记第35条——了解sync包的正确用法
    Go语言通过标准库的sync包提供了针对传统基于共享内存并发模型的基本同步原语。35.1sync包还是channel在下面一些场景下,我们依然需要sync包提供的低级同步原语(1)需要高性能的临界区同步机制场景(2)不想转移结构体对象所有权,但又要保证结构体内部状态数据的同步访问的场景......
  • 《程序是怎样跑起来的》第8章读书笔记
    了解了源文件,就要了解怎样执行文件。首先用某种编程语言编写的程序称为源代码,将源代码保存成一个文件就称为源文件源代码是不能直接运行的,因为CPU能直接解释和执行的,只有本机代码,所以必须翻译成本机代码才能被CPU理解和执行。而windows的exe文件中的程序内容就是本机代码转组是指......
  • Go语言精进之路读书笔记第34条——了解channel的妙用
    c:=make(chanint)//创建一个无缓冲(unbuffered)的int类型的channelc:=make(chanint,5)//创建一个带缓冲的int类型的channelc<-x//向channelc中发送一个值<-c//从channelc中接收一个值x=<-c//从channelc接收一个值并......
  • 《程序是怎样跑起来的》第7章读书笔记
    第7章就把重点放到了这本书程序是怎么跑起来的重点上,但同时也难理解了许多。我们知道的是程序要在特定的运行环境上才能运行,而运行环境等于操作系统加硬盘,每个程序都有其对应的运行环境操作系统和硬件决定了程序的运行环境,还需要知道的是,在将硬件作为程序运行环境考虑是CPU的类型......
  • 《程序是怎样跑起来的》第6章读书笔记
    前面讲述了内存跟磁盘,而内存跟磁盘里面的储存量也是有限的,那么我们就需要去压缩数据,而数据该怎么压缩呢?第6章就为我们介绍了。首先要了解文件中储存数据的格式文件是在磁盘等储存媒体中储存数据的一种形式,程序是以字节为单位向文件中储存数据的储存在文件中的数据。如果表示字符,那......
  • Go语言精进之路读书笔记第33条——掌握Go并发模型和常见并发模式
    不要通过共享内存来通信,而应该通过通信来共享内存。——RobPike33.1Go并发模型CSP(CommunicatingSequentialProcess,通信顺序进程)模型。一个符合CSP模型的并发程序应该是一组通过输入/输出原语连接起来的P的集合。Go始终推荐以CSP模型风格构建并发程序。Go针对CSP模型提供......
  • 《程序是怎么跑起来的》第5章读书笔记
    第4张介绍了内存那么第5张就是磁盘。在开篇告诉了我们内存只主存而磁盘主要指硬盘。计算机中的储存器包括内存和磁盘储存在磁盘中的程序需要先加载到内存才能运行,不能在磁盘上直接运行。内存与磁盘的联系是非常密切的。第1个体现是磁盘缓存。磁盘缓存是一块内存空间,用于临时存放从......
  • 《程序是怎么跑起来的》第4章读书笔记
    计算机是处理数据的机器,而处理对象的数据储存在内存和磁盘中。内存本质上是一种名为内存芯片的装置,内存芯片分为ram,rom等不同类型,但从外部来看,它们的基本原理是相同的内存芯片外部有引脚负责连接电源以及输入地址信号等等。内存芯片内部有很多能储存巴比特数据的容器,只要指定容器......
  • 《程序是怎么跑起来的》第3章读书笔记
    经过前两章对计算机内容最基本的理解之后,就迎来了对计算机的计算,而计算机也不是万能的,它也会出现错误,那么就涉及到计算机在计算小数时会出现错误的原因,首先课题通过一个问题将0.1累加100次的结果不是10这一话题成功将读者引入进去。然后告诉了我们为什么在计算机中会这样子出错的......
  • 《程序是怎样跑起来的》第五章读书笔记
    从都具有存储程序命令和数据这点来看,内存和磁盘的功能是相同的。在计算机的五大部位中,内存和磁盘也都也都被归类为存储部件。不过利用电流来实现存储的内存,同利用磁效应来实现存储的磁盘,还是有差异的,而从存储容量来看,内存是告诉高价,而磁盘则是低速廉价。程序保护在存储设备中,通过......