Skip to main content
 首页 » 编程设计

Golang检测并处理竞争条件

2022年07月19日129grandyang

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

什么是竞争条件

竞争条件一般意味着数据竞争,如一些资源被多个线程或协程访问。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同步代码。再次运行不会报错,并正常输出结果。

总结

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


本文参考链接:https://blog.csdn.net/neweastsun/article/details/123012412
阅读延展