Golang保护共享资源,处理race condition的三种方式

本文探讨了Go语言中多个协程修改同一资源时可能遇到的竞态条件,通过举例说明了sync.Mutex、atomic包和channel如何避免race condition,并展示了使用它们确保数据一致性的方式。
摘要由CSDN通过智能技术生成

我们先来看一个例子,多个协程修改同一个资源会发生什么?
在这里插入图片描述
如上图所示,多个协程对同一个资源进行修改,可能出现竞争关系,导致数据被覆盖,得到不想要的结果。

原因:
我们修改一个变量的过程是分成了三个步骤:

  1. 创建一个副本,从内存中拷贝数据到副本
  2. 对副本进行相加计算
  3. 把副本的值拷贝到内存中,覆盖原来的值

我们可以看到这三个操作并不是原子性的,可能出现协程1获取到数据后,还没来得及修改数据就被协程2给覆盖了。

在golang中,我们可以通过以下三种方式进行保护共享资源,处理race condition。

1、通过sync.Mutex进行加锁
var mtx sync.Mutex
var wg sync.WaitGroup
var counter int32

func main() {
	wg.Add(2)

	go mutexIncrCounter()
	go mutexIncrCounter()

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

// 通过mutex加锁
func mutexIncrCounter() {
	defer wg.Done()

	for i := 0; i < 1000; i++ {
		mtx.Lock()
		counter++
		mtx.Unlock()
	}
}

// 运行结果 counter:  2000
2、使用atomic进行原子操作
var wg sync.WaitGroup
var counter int32

func main() {
	wg.Add(2)

	go atomicIncrCounter()
	go atomicIncrCounter()

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

// 通过atomic包实现原子操作
func atomicIncrCounter() {
	defer wg.Done()

	for i := 0; i < 1000; i++ {
		atomic.AddInt32(&counter, 1)
	}
}
// 运行结果 counter:  2000
3、利用channel进行数据同步(channel底层实现时用到了互斥锁和缓冲数据队列,在元素进行出队/入队时都通过锁机制保障了操作的原子性,避免了复杂的竞态情形)
var wg sync.WaitGroup
var counter int32
var ch = make(chan int32, 0)

func main() {
	wg.Add(2)

	go channelIncrCounter()
	go channelIncrCounter()

	ch <- 0

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

// 通过channel处理race condition
func channelIncrCounter() {
	defer wg.Done()

	for i := 0; i < 1000; i++ {
		counter = <-ch
		counter++
		ch <- counter
	}
	_, ok := <-ch
	if ok {
		close(ch)
	}
}
// 运行结果 counter:  2000
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值