showliuzp 2025-11-06 12:46 采纳率: 84.3%
浏览 7
已结题

golang map多协程写入、删除没有报fatal error


package main

import (
    "fmt"
    "sync"
)

var maps = make(map[int]string,0)

func main() {
    var wg sync.WaitGroup
    wg.Add(4)

    go write_call(&wg)
    go write_calll(&wg)
    go write_callll(&wg)
    go read_call(&wg)
    wg.Wait()
}

func read_call(wg *sync.WaitGroup) {
    for{
        fmt.Printf("输出:%+v\n",maps)
    }

    wg.Done()
}

func write_call(wg *sync.WaitGroup) {
    for{
        maps[1] = "aa"
        fmt.Printf("写入\n")
    }

    wg.Done()
}

func write_calll(wg *sync.WaitGroup) {
    for{
        maps[2] = "bb"
        fmt.Printf("写入\n")
    }

    wg.Done()
}


func write_callll(wg *sync.WaitGroup) {
    for{
        delete(maps,3)
        //maps[3] = "cc"
        fmt.Printf("删除\n")
    }

    wg.Done()
}

//我在测试多协程写入map的安全性,CPU两核。按照golang map的实现,多个协程写入同一个map应该报fatal error,或者多个写,一个删除也应该fatal error报错,我循环调用了几十万次,也没有报错。
  • 写回答

3条回答 默认 最新

  • 宝码香车 优质创作者: JavaScript技术领域 2025-11-06 13:56
    关注

    让【宝码香车】来帮您解答,本回答参考DeepSeek编写,并整理提供。

    如果答案让您满意,请采纳、点赞、关注,非常感谢!
    .
    每日一句正能量,让它带给您好心情:生活的美好,在于它的不确定性,你永远不知道下一秒会有什么惊喜。
    .

    根据你提供的代码和描述,我来解释为什么没有出现 fatal error

    问题分析

    你的代码确实存在并发读写map的安全问题,但Go的map并发检测机制并不是100%实时触发的。以下是主要原因:

    1. 竞态条件检测的局限性

    Go的map并发检测是基于内存访问冲突的,它只在检测到同时进行的读写操作时才会触发panic。由于你的操作是循环执行的,可能在某些时刻没有发生真正的冲突。

    2. 操作模式的影响

    在你的代码中:

    • 三个写协程操作不同的key(1, 2, 3)
    • 读协程只是读取整个map
    • 删除操作针对不存在的key(3)

    这种模式可能减少了真正的内存冲突概率。

    3. CPU调度的影响

    在两核CPU上,协程的调度相对稀疏,进一步降低了冲突的概率。

    重现问题的方法

    要可靠地重现并发写map的panic,可以使用以下改进的测试代码:

    package main
    
    
    import (
        "fmt"
        "sync"
    )
    
    
    var maps = make(map[int]string, 0)
    var wg sync.WaitGroup
    
    
    func main() {
        // 增加协程数量,操作相同的key
        wg.Add(3)
        
        // 多个协程同时读写相同的key
        go concurrentWrite(1)
        go concurrentWrite(1) 
        go concurrentRead(1)
        
        wg.Wait()
    }
    
    
    func concurrentWrite(key int) {
        defer wg.Done()
        for i := 0; i < 100000; i++ {
            maps[key] = fmt.Sprintf("value%d", i)
            // 频繁在写入和删除之间切换
            if i%2 == 0 {
                delete(maps, key)
            }
        }
    }
    
    
    func concurrentRead(key int) {
        defer wg.Done()
        for i := 0; i < 100000; i++ {
            _ = maps[key] // 读取操作
        }
    }
    

    正确的并发map使用方法

    方法1:使用sync.Mutex

    package main
    
    
    import (
        "fmt"
        "sync"
    )
    
    
    type SafeMap struct {
        m   map[int]string
        mut sync.RWMutex
    }
    
    
    func NewSafeMap() *SafeMap {
        return &SafeMap{
            m: make(map[int]string),
        }
    }
    
    
    func (s *SafeMap) Set(key int, value string) {
        s.mut.Lock()
        defer s.mut.Unlock()
        s.m[key] = value
    }
    
    
    func (s *SafeMap) Get(key int) (string, bool) {
        s.mut.RLock()
        defer s.mut.RUnlock()
        value, exists := s.m[key]
        return value, exists
    }
    
    
    func (s *SafeMap) Delete(key int) {
        s.mut.Lock()
        defer s.mut.Unlock()
        delete(s.m, key)
    }
    

    方法2:使用sync.Map(Go 1.9+)

    package main
    
    
    import (
        "fmt"
        "sync"
    )
    
    
    func main() {
        var sm sync.Map
        var wg sync.WaitGroup
        wg.Add(4)
        
        go func() {
            defer wg.Done()
            for i := 0; i < 1000; i++ {
                sm.Store(1, "aa")
            }
        }()
        
        go func() {
            defer wg.Done() 
            for i := 0; i < 1000; i++ {
                sm.Store(2, "bb")
            }
        }()
        
        go func() {
            defer wg.Done()
            for i := 0; i < 1000; i++ {
                sm.Delete(3)
            }
        }()
        
        go func() {
            defer wg.Done()
            for i := 0; i < 1000; i++ {
                if value, ok := sm.Load(1); ok {
                    _ = value
                }
            }
        }()
        
        wg.Wait()
    }
    

    总结

    1. 你的代码确实存在并发安全问题,只是由于调度和操作模式的原因没有立即触发panic
    2. 不要依赖未定义行为,即使现在不panic,也不能保证在其他环境或条件下不出现问题
    3. 始终使用适当的同步机制来保护map的并发访问

    建议使用 go run -race your_program.go 来检测竞态条件,这会比运行时panic更早发现问题。

    本回答被题主选为最佳回答 , 对您是否有帮助呢?
    评论
查看更多回答(2条)

报告相同问题?

问题事件

  • 系统已结题 11月15日
  • 已采纳回答 11月7日
  • 创建了问题 11月6日