首页 文章 Go语言基础 Go并发编程之传统同步 — 原子操作
0
0
0
47

Go并发编程之传统同步 — 原子操作

并发控制 原子 传统

前言

之前文章中介绍的互斥锁虽然能够保证同串行化,但是却保证不了执行过程中的中断。
要么成功、要么失败,没有中断的情况,我们叫它叫原子性,这种由硬件 CPU 提供支持的特性,是非常可靠的。

百度百科上关于原子操作的介绍。

原子操作

由 sync/atomic 包提供操作支持。

加法(add)

实现累加

func TestDemo1(t *testing.T) {
    var counter int64 = 0

    for i := 0; i < 100; i++ {
        go func() {
            atomic.AddInt64(&counter, 1)
        }()
    }

    time.Sleep(2 * time.Second)
    log.Println("counter:", atomic.LoadInt64(&counter))
}

结果

=== RUN   TestDemo1
2020/10/11 00:24:56 counter: 100
--- PASS: TestDemo1 (2.00s)
PASS

减法(add)

对于做减法,是没有直接提供的方法的,而 Add(-1)这种是不能对 uint 类型使用的,可以通过补码的方式实现

func TestDemo2(t *testing.T) {
    var counter uint64 = 100

    for i := 0; i < 100; i++ {
        go func() {
            atomic.AddUint64(&counter, ^uint64(-(-1)-1))
        }()
    }

    time.Sleep(2 * time.Second)
    log.Println("counter:", atomic.LoadUint64(&counter))
}

结果

=== RUN   TestDemo2
2020/10/11 00:32:05 counter: 0
--- PASS: TestDemo2 (2.00s)
PASS

比较并交换(compare and swap,简称 CAS)

并发编程中,在没有使用互斥锁的前提下,对共享数据先取出做判断,再根据判断的结果做后续操作,必然是会出问题的,使用 CAS 可以避免这种问题。

func TestDemo3(t *testing.T) {
    var first int64 = 0

    for i := 1; i <= 10000; i++ {
        go func(i int) {
            if atomic.CompareAndSwapInt64(&first, 0, int64(i)) {
                log.Println("抢先运行的是 goroutine", i)
            }
        }(i)
    }

    time.Sleep(2 * time.Second)
    log.Println("num:", atomic.LoadInt64(&first))
}

结果

=== RUN   TestDemo3
2020/10/11 00:42:10 抢先运行的是 goroutine 3
2020/10/11 00:42:12 num: 3
--- PASS: TestDemo3 (2.01s)
PASS

加载(load)

加载操作在进行时只会有一个,不会有其它的读写操作同时进行。

func TestDemo4(t *testing.T) {
    var counter int64 = 0

    for i := 0; i < 100; i++ {
        go func() {
            atomic.AddInt64(&counter, 1)
            log.Println("counter:", atomic.LoadInt64(&counter))
        }()
    }

    time.Sleep(2 * time.Second)
}

存储(store)

存储操作在进行时只会有一个,不会有其它的读写操作同时进行。

func TestDemo5(t *testing.T) {
    var counter int64 = 0

    for i := 0; i < 10; i++ {
        go func(i int) {
            atomic.StoreInt64(&counter, int64(i))
            log.Println("counter:", atomic.LoadInt64(&counter))
        }(i)
    }

    time.Sleep(2 * time.Second)
}

交换(swap)

swap 方法返回被替换之前的旧值。

func TestDemo6(t *testing.T) {
    var counter int64 = 0

    for i := 0; i < 10; i++ {
        go func(i int) {
            log.Println("counter old:", atomic.SwapInt64(&counter, int64(i)))
        }(i)
    }

    time.Sleep(2 * time.Second)
}

结果

=== RUN   TestDemo6
2020/10/11 00:43:36 counter old: 0
2020/10/11 00:43:36 counter old: 9
2020/10/11 00:43:36 counter old: 5
2020/10/11 00:43:36 counter old: 1
2020/10/11 00:43:36 counter old: 2
2020/10/11 00:43:36 counter old: 3
2020/10/11 00:43:36 counter old: 6
2020/10/11 00:43:36 counter old: 4
2020/10/11 00:43:36 counter old: 7
2020/10/11 00:43:36 counter old: 0
--- PASS: TestDemo6 (2.00s)
PASS

原子值(value)

value是一个结构体,内部值定义为 interface{},所以它是可以接受任何类型的值。

第一次赋值的时候,原子值的类型就确认了,后面不能赋值其它类型的值。

func TestDemo7(t *testing.T) {
    var value atomic.Value
    var counter uint64 = 1

    value.Store(counter)
    log.Println("counter:", value.Load())

    value.Store(uint64(10))
    log.Println("counter:", value.Load())

    value.Store(100) // 引发 panic
    log.Println("counter:", value.Load())

    time.Sleep(2 * time.Second)
}

结果

=== RUN   TestDemo7
2020/10/11 10:14:58 counter: 0
2020/10/11 10:14:58 counter: 10
--- FAIL: TestDemo7 (0.00s)
panic: sync/atomic: store of inconsistently typed value into Value [recovered]
    panic: sync/atomic: store of inconsistently typed value into Value
                ...
Process finished with exit code 1


到此这篇关于“Go并发编程之传统同步 — 原子操作”的文章就介绍到这了,更多文章或继续浏览下面的相关文章,希望大家以后多多支持Go语言编程网!

相关文章

创建博客

开始创作
写作能提升自己能力,也能为他人分享知识。

在线教程

查看更多
  • Go入门指南

    Go入门指南

  • Go语言高级编程

    Go语言高级编程

  • Go Web 编程

    Go Web 编程

  • GO专家编程

    GO专家编程

  • Go语言四十二章经

    Go语言四十二章经

  • 数据结构和算法(Golang实现)

    数据结构和算法(Golang实现)

Go语言编程网

微信扫码关注订阅号


博客 资讯 教程 我的