运维八一 运维八一
首页
运维杂记
编程浅尝
周积跬步
专栏
生活
关于
收藏
  • 分类
  • 标签
  • 归档
Source (opens new window)

运维八一

运维,运维!
首页
运维杂记
编程浅尝
周积跬步
专栏
生活
关于
收藏
  • 分类
  • 标签
  • 归档
Source (opens new window)
  • Go

    • 前言

    • Go基础知识

    • Go基本语法

    • 实战项目:简单web服务

    • 基本数据类型

    • 内置运算符

    • 分支和循环

    • 函数 function

    • 结构体 struct

    • 方法 method

    • 实战项目:跟踪函数调用链

    • 接口 interface

    • 并发 concurrency

      • 什么是并发
      • goroutine
      • select
      • channel
      • 基于共享内存的并发模型
      • 原子操作 atomic包
        • 6. 原子操作 atomic 包
    • 指针

    • 实战项目:实现轻量级线程池

    • 实战项目:实现TCP服务器

    • go常用包

    • Gin框架

    • go随记

  • Python

  • Shell

  • Java

  • Vue

  • 前端

  • 编程浅尝
  • Go
  • 并发 concurrency
lyndon
2022-06-07
目录

原子操作 atomic包

# 6. 原子操作 atomic 包

面向 CSP 并发模型的 channel 原语和面向传统共享内存并发模型的 sync 包提供的原语,已经能够满足 Go 语言应用并发设计中 99.9% 的并发同步需求了。而剩余那 0.1% 的需求,可以使用 Go 标准库提供的 atomic 包来实现。

atomic 包是 Go 语言给用户提供的原子操作原语的相关接口。原子操作(atomic operations)是相对于普通指令操作而言的。

整型变量自增的语句示例:

var a int
a++
1
2

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

  • LOAD:将变量从内存加载到 CPU 寄存器;
  • ADD:执行加法指令;
  • STORE:将结果存储回原内存地址中。

这 3 条普通指令在执行过程中是可以被中断的。而原子操作的指令是不可中断的,它就好比一个事务,要么不执行,一旦执行就一次性全部执行完毕,中间不可分割。也正因为如此,原子操作也可以被用于共享数据的并发同步。

原子操作由底层硬件直接提供支持,是一种硬件实现的指令级的“事务”,因此相对于操作系统层面和 Go 运行时层面提供的同步技术而言,它更为原始。

atomic 包封装了 CPU 实现的部分原子操作指令,为用户层提供体验良好的原子操作函数,因此 atomic 包中提供的原语更接近硬件底层,也更为低级,它也常被用于实现更为高级的并发同步技术,比如 channel 和 sync 包中的同步原语。

atomic 包提供了两大类原子操作接口:

  • 一类是针对整型变量的,包括有符号整型、无符号整型以及对应的指针类型;
  • 一类是针对自定义类型的。

因此,第一类原子操作接口的存在让 atomic 包天然适合去实现某一个共享整型变量的并发同步。

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

// 分别在 cpu=2、 8、16、32 的情况下运行上述性能基准测试,得到结果如下:
/*
goos: darwin
goarch: amd64
... ...
BenchmarkAddSyncByAtomic-2       75426774          17.69 ns/op
BenchmarkReadSyncByAtomic-2      1000000000           0.7437 ns/op
BenchmarkAddSyncByRWMutex-2      39041671          30.16 ns/op
BenchmarkReadSyncByRWMutex-2     41325093          28.48 ns/op

BenchmarkAddSyncByAtomic-8       77497987          15.25 ns/op
BenchmarkReadSyncByAtomic-8      1000000000           0.2395 ns/op
BenchmarkAddSyncByRWMutex-8      17702034          67.16 ns/op
BenchmarkReadSyncByRWMutex-8     29966182          40.37 ns/op

BenchmarkAddSyncByAtomic-16        57727968          20.39 ns/op
BenchmarkReadSyncByAtomic-16       1000000000           0.2536 ns/op
BenchmarkAddSyncByRWMutex-16       15029635          78.61 ns/op
BenchmarkReadSyncByRWMutex-16      29722464          40.28 ns/op

BenchmarkAddSyncByAtomic-32        58010497          20.40 ns/op
BenchmarkReadSyncByAtomic-32       1000000000           0.2402 ns/op
BenchmarkAddSyncByRWMutex-32       11748312          93.15 ns/op
BenchmarkReadSyncByRWMutex-32      29845912          40.54 ns/op
*/
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84

结论:

  • 读写锁的性能随着并发量增大的情况,与前面讲解的 sync.RWMutex 一致;
  • 利用原子操作的无锁并发写的性能,随着并发量增大几乎保持恒定;
  • 利用原子操作的无锁并发读的性能,随着并发量增大有持续提升的趋势,并且性能是读锁的约 200 倍。

atomic 原子操作的特性:

随着并发量提升,使用 atomic 实现的共享变量的并发读写性能表现更为稳定,尤其是原子读操作,和 sync 包中的读写锁原语比起来,atomic 表现出了更好的伸缩性和高性能。

atomic 包应用场景:

适合一些对性能十分敏感、并发量较大且读多写少的场合。

atomic 原子操作可用来同步的范围有比较大限制,只能同步一个整型变量或自定义类型变量。 如果对一个复杂的临界区数据进行同步,首选依旧是 sync 包。

上次更新: 2022/06/12, 15:48:09
基于共享内存的并发模型
指针类型

← 基于共享内存的并发模型 指针类型→

最近更新
01
ctr和crictl显示镜像不一致
03-13
02
alpine镜像集成常用数据库客户端
03-13
03
create-cluster
02-26
更多文章>
Theme by Vdoing | Copyright © 2015-2024 op81.com
苏ICP备18041258号-2
  • 跟随系统
  • 浅色模式
  • 深色模式
  • 阅读模式