Golang并发Map中的竞态问题 - 原因分析与解决
Golang并发Map中的竞态问题 - 原因分析与解决 我创建了一个由互斥锁保护的Go并发映射,但当两个goroutine同时修改该映射时,Go仍然会因并发映射访问和映射写入异常而引发恐慌。我不知道还能采取什么措施来阻止这种竞态条件的发生。
package main
import (
"os"
"fmt"
"io/ioutil"
"strings"
"sync"
)
type concurrentMap struct{
sync.Mutex
urls map[string]int
}
func (c *concurrentMap) addToMap(url string) {
c.Lock()
if val , ok := c.urls[url];ok{
c.urls[url] = val + 1
}else{
c.urls[url] = 1
}
c.Unlock()
}
func NewConcurrentMap() *concurrentMap {
return &concurrentMap{
urls:make(map[string]int),
}
}
func (c *concurrentMap) getUrls() map[string]int {
return c.urls
}
func main(){
c := NewConcurrentMap()
var waitGroup sync.WaitGroup
file_path := os.Args[1]
contents , err := ioutil.ReadFile(file_path)
if err != nil {
fmt.Println(err)
return
}
fileContents := string(contents)
lines := strings.Split(fileContents,"\n")
half := len(lines) / 2
go func(subset []string){
waitGroup.Add(1)
defer waitGroup.Done()
for _,line := range subset {
data := strings.Split(line,",")
if len(data) < 2{
continue
}
url := data[1]
c.addToMap(url)
}
}(lines[0:half])
go func(subset []string){
waitGroup.Add(1)
defer waitGroup.Done()
for _,line := range subset {
data := strings.Split(line,",")
if len(data) < 2{
continue
}
url := data[1]
c.addToMap(url)
}
}(lines[half+1:])
waitGroup.Wait()
fmt.Println(c.getUrls())
}
更多关于Golang并发Map中的竞态问题 - 原因分析与解决的实战教程也可以访问 https://www.itying.com/category-94-b0.html
2 回复
我认为问题在于您在goroutine内部将goroutine添加到等待组中。然后
waitGroup.Add(1)
go func(subset []string){
defer waitGroup.Done()
for _,line := range subset {
data := strings.Split(line,",")
if len(data) < 2{
continue
}
url := data[1]
c.addToMap(url)
}
}(lines[0:half])
waitGroup.Add(1)
go func(subset []string){
defer waitGroup.Done()
for _,line := range subset {
data := strings.Split(line,",")
if len(data) < 2{
continue
}
url := data[1]
c.addToMap(url)
}
}(lines[half+1:])
waitGroup.Wait()
更多关于Golang并发Map中的竞态问题 - 原因分析与解决的实战系列教程也可以访问 https://www.itying.com/category-94-b0.html
在你的代码中,存在几个导致竞态条件的问题。主要问题在于WaitGroup的使用方式不正确,以及getUrls()方法没有进行同步保护。
问题分析:
WaitGroup.Add(1)应该在goroutine外部调用,而不是在goroutine内部getUrls()方法返回了映射的引用,这可能导致在读取时发生并发访问- 切片分割时可能存在索引越界问题
修正后的代码:
package main
import (
"fmt"
"io/ioutil"
"os"
"strings"
"sync"
)
type concurrentMap struct {
sync.RWMutex
urls map[string]int
}
func (c *concurrentMap) addToMap(url string) {
c.Lock()
defer c.Unlock()
if val, ok := c.urls[url]; ok {
c.urls[url] = val + 1
} else {
c.urls[url] = 1
}
}
func NewConcurrentMap() *concurrentMap {
return &concurrentMap{
urls: make(map[string]int),
}
}
func (c *concurrentMap) getUrls() map[string]int {
c.RLock()
defer c.RUnlock()
// 返回副本而不是原始映射的引用
result := make(map[string]int)
for k, v := range c.urls {
result[k] = v
}
return result
}
func main() {
if len(os.Args) < 2 {
fmt.Println("请提供文件路径参数")
return
}
c := NewConcurrentMap()
var waitGroup sync.WaitGroup
file_path := os.Args[1]
contents, err := ioutil.ReadFile(file_path)
if err != nil {
fmt.Println(err)
return
}
fileContents := string(contents)
lines := strings.Split(fileContents, "\n")
half := len(lines) / 2
// 在goroutine外部调用Add
waitGroup.Add(2)
go func(subset []string) {
defer waitGroup.Done()
for _, line := range subset {
data := strings.Split(line, ",")
if len(data) < 2 {
continue
}
url := data[1]
c.addToMap(url)
}
}(lines[0:half])
go func(subset []string) {
defer waitGroup.Done()
for _, line := range subset {
data := strings.Split(line, ",")
if len(data) < 2 {
continue
}
url := data[1]
c.addToMap(url)
}
}(lines[half:]) // 修正索引:使用half而不是half+1
waitGroup.Wait()
fmt.Println(c.getUrls())
}
主要改进:
- 使用
defer c.Unlock()确保锁一定会被释放 - 将
WaitGroup.Add(2)移到goroutine外部,确保在goroutine开始执行前计数器已增加 - 将
sync.Mutex改为sync.RWMutex,在读取时使用读锁 - 在
getUrls()方法中添加读锁保护,并返回映射的副本而不是引用 - 修正切片分割的索引问题
- 添加参数检查
这些修改应该能解决你遇到的竞态条件问题。

