Golang 原子操作与互斥锁

Golang 原子操作与互斥锁

先来看一个代码

package main

import (
	"fmt"
	"runtime"
	"sync"
)

var (
	counter  int32
	wg sync.WaitGroup
)

func main() {
	wg.Add(2) // 设置需要等待的 goroutine 的数量为 2

	go addCounter("Gerald")
	go addCounter("Seligman")

	wg.Wait()
	fmt.Println("Final Counter is: %+v", counter)
}

func addCounter(whoAmI string) {
	defer wg.Done()

	for count := 0; count < 2;  count++ {
		value := counter

		// Gosched使当前go程放弃处理器,以让其它go程运行。它不会挂起当前go程,因此当前go程未来会恢复执行。
		runtime.Gosched()

		value++

		counter = value
	}
}

Final Counter is: 2

首先这个程序是起了两个 goroutine 。每个 goroutine 都对 counter 进行了两次累加操作,所以理论上 counter 最后应该是 4 而不是 2 。

原因是因为,Gerald 协程获取到 counter 的值后,又让 Seligman 协程去获取 counter 的值。注意此时 counter 的值还没改变,所以他们两个协程都拿到 0 这个值。然后两个协程又将 value++ 后赋值给 counter,等于说做了两次 counter = 1 的操作。第二次循环也是一样的原理。所以最后的值是 2 。

这种情况可以用两中方式避免

  • Atomic 原子操作
  • Mutex 互斥锁

Atomic

for count := 0; count < 2;  count++ {
		value := counter

		// Gosched使当前go程放弃处理器,以让其它go程运行。它不会挂起当前go程,因此当前go程未来会恢复执行。
		runtime.Gosched()

		value++

		counter = value
	}

这段代码其实有点奇怪,为什么给 counter + 1 要写得这么多步。这其实是在模拟 CPU 内部再给你个值+1的时候需要做的操作。首先寄存器会把 counter 的值拿出来保存在寄存器里,然后在寄存器里进行+1操作,最后再把寄存器里的值放到CPU里(原理是这样,若位置错误,请及时提醒,见谅)。

而 Atomic 就是相当于在 CPU 里加锁,让这三步操作在执行的时候,当前协程不可以被调度切换,等这三步完成之后才可以被调度切换。

package main

import (
	"fmt"
	"runtime"
	"sync"
	"sync/atomic"
)

var (
	counter  int32
	wg sync.WaitGroup
)

func main() {
	wg.Add(2) // 设置需要等待的 goroutine 的数量为 2

	go addCounter("Gerald")
	go addCounter("Seligman")

	wg.Wait()
	fmt.Println("Final Counter is: ", counter)
}

func addCounter(whoAmI string) {
	defer wg.Done()

	for count := 0; count < 2;  count++ {
		//value := counter
		//
		 Gosched使当前go程放弃处理器,以让其它go程运行。它不会挂起当前go程,因此当前go程未来会恢复执行。
		//runtime.Gosched()
		//
		//value++
		//
		//counter = value

		atomic.AddInt32(&counter, 1)
		runtime.Gosched()
	}
}

Final Counter is: 4

这次 counter 就变成 4 了,符合预期。

Mutex

这个应该很熟悉了,互斥锁,就是在对元素操作之前进行加锁,操作完了解锁。就可以保证数据一致了。

package main

import (
	"fmt"
	"sync"
)

var (
	counter  int32
	wg sync.WaitGroup
	mutex sync.Mutex
)

func main() {
	wg.Add(2) // 设置需要等待的 goroutine 的数量为 2

	go addCounter("Gerald")
	go addCounter("Seligman")

	wg.Wait()
	fmt.Println("Final Counter is: ", counter)
}

func addCounter(whoAmI string) {
	defer wg.Done()

	for count := 0; count < 2;  count++ {
		//value := counter
		//
		 Gosched使当前go程放弃处理器,以让其它go程运行。它不会挂起当前go程,因此当前go程未来会恢复执行。
		//runtime.Gosched()
		//
		//value++
		//
		//counter = value

		//atomic.AddInt32(&counter, 1)
		//runtime.Gosched()

		mutex.Lock()
		counter++
		mutex.Unlock()
	}
}

Final Counter is: 4

叮~🔔

  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值