Golang检测并处理竞争条件

本文详细介绍了Go语言中的竞争条件,即当多个线程同时访问非线程安全的资源时可能出现的问题。通过示例代码展示了如何检测竞争条件,使用`go run -race`或`go build -race`命令。文章还解释了如何通过引入sync包中的互斥锁修复竞争条件,确保并发安全。修复后的代码避免了并发写入map导致的错误,从而保证了程序的正确运行。

本文我们介绍竞争条件,包括如何检测竞争条件以及如何修复该问题。

什么是竞争条件

竞争条件一般意味着数据竞争,如一些资源被多个线程或协程访问。Golong中资源可能为struct、map、变量等,大多数资源如果不显示声明都不是并发或线程安全的。
在这里插入图片描述

典型的线程安全示例为,一些数据库连接被创建为全局变量,它能够在多处并发使用。请看下面示例:

import (
	"fmt"
	"sync"
)

func main() {
	fmt.Println("Start")

	m := make(map[int]struct{})
	wg := &sync.WaitGroup{}

	for i := 0; i<500; i++{
		wg.Add(1)
		go updateMap(wg, m, i)
	}

	wg.Wait()
	fmt.Println(m)
}

func updateMap(wg *sync.WaitGroup, m map[int]struct{}, r int) {
	defer wg.Done()
	m[r] = struct{}{}
}

上面代码创建了map并传入updateMap函数,在函数中根据键更新map。运行代码输出错误:

fatal error: concurrent map writes
因为map不是线程安全的,这就是竞争条件的一种场景。当然这种错误可能不会出现,这取决于你如何编写应用代码(是否会造成并发访问资源)。

检测竞争条件

上节我们说明了如何可能发生竞争条件,下面介绍在这之前如何检测。Go提供了标识选型,可以在run/build应用程序时进行检测。请看示例:

示例文件名称为race.go ,可以通过下面命令检测竞争条件:

go run -race main.go 或者 go build -race main.go

 go run -race main.go
Start
==================
WARNING: DATA RACE
Write at 0x00c000144450 by goroutine 8:
  runtime.mapassign_fast64()
      E:/dev-tools/go1.17/src/runtime/map_fast64.go:92 +0x0
  main.updateMap()
      D:/workspace/golang/wordcount/main.go:25 +0x9e
  main.main路dwrap路1()
      D:/workspace/golang/wordcount/main.go:16 +0x58

Previous write at 0x00c000144450 by goroutine 7:
  runtime.mapassign_fast64()
      E:/dev-tools/go1.17/src/runtime/map_fast64.go:92 +0x0
  main.updateMap()
      D:/workspace/golang/wordcount/main.go:25 +0x9e
  main.main路dwrap路1()
      D:/workspace/golang/wordcount/main.go:16 +0x58

Goroutine 8 (running) created at:
  main.main()
      D:/workspace/golang/wordcount/main.go:16 +0xc9

Goroutine 7 (finished) created at:
  main.main()
      D:/workspace/golang/wordcount/main.go:16 +0xc9
==================
fatal error: concurrent map writes
.....

提醒:通过race选项检测竞争条件非常耗时,一般不要对生产环境应用使用在选项,仅在编码阶段使用。

修复竞争条件

下面看如何修复竞争条件问题,Go提供sync包中互斥锁可以实现同步机制进行解决。
首先创建互斥锁: mx := &sync.Mutex{}, 它提供了两个方法 Lock 和 Unlock ,利用它们可以锁定代码片段,让代码被同步执行。

完整代码:


import (
	"fmt"
	"sync"
)

func main() {
	fmt.Println("Start")

	m := make(map[int]struct{})
	wg := &sync.WaitGroup{}
	mx := &sync.Mutex{}

	for i := 0; i<500; i++{
		wg.Add(1)
		go updateMap(wg, mx, m, i)
	}

	wg.Wait()
	fmt.Println(m)
}

func updateMap(wg *sync.WaitGroup, mx *sync.Mutex, m map[int]struct{}, r int) {
	defer wg.Done()
	mx.Lock()
	defer mx.Unlock()
	m[r] = struct{}{}
}

主要修改了updateMap函数,利用Lock和Unlock同步代码。再次运行不会报错,并正常输出结果。

总结

本文学习了什么是竞争条件,如何检测并通过互斥锁进行修复。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值