MST

星途 面试题库

面试题:Go语言Map删除元素在并发场景下的问题与解决方案

在Go语言的并发编程中,有多个goroutine同时对一个map[string]int类型的映射变量m进行读写操作,其中部分goroutine会执行删除元素的操作。请分析可能出现的问题,并提出至少两种有效的解决方案,要求给出相应的代码示例及原理说明。
48.2万 热度难度
编程语言Go

知识考点

AI 面试

面试题答案

一键面试

可能出现的问题

在Go语言中,map不是线程安全的。当多个goroutine同时对map进行读写操作,尤其是包含删除操作时,可能会出现以下问题:

  1. 数据竞争:这会导致未定义行为,程序可能崩溃或者产生不可预期的结果。

解决方案及示例代码

  1. 使用sync.Mutex
    • 原理:通过互斥锁(Mutex)来保证同一时间只有一个goroutine可以对map进行读写或删除操作,从而避免数据竞争。
    • 代码示例
package main

import (
    "fmt"
    "sync"
)

func main() {
    var mu sync.Mutex
    m := make(map[string]int)
    var wg sync.WaitGroup

    for i := 0; i < 10; i++ {
        wg.Add(1)
        go func(id int) {
            defer wg.Done()
            key := fmt.Sprintf("key%d", id)
            mu.Lock()
            m[key] = id
            fmt.Printf("goroutine %d inserted key: %s, value: %d\n", id, key, id)
            mu.Unlock()
        }(i)
    }

    for i := 0; i < 5; i++ {
        wg.Add(1)
        go func(id int) {
            defer wg.Done()
            key := fmt.Sprintf("key%d", id)
            mu.Lock()
            delete(m, key)
            fmt.Printf("goroutine %d deleted key: %s\n", id, key)
            mu.Unlock()
        }(i)
    }

    wg.Wait()
    fmt.Println("Final map:", m)
}
  1. 使用sync.RWMutex
    • 原理:读写锁(RWMutex)允许读操作并发执行,但写操作(包括删除操作)必须独占。这在有较多读操作和较少写操作的场景下,可以提高性能。
    • 代码示例
package main

import (
    "fmt"
    "sync"
)

func main() {
    var rwmu sync.RWMutex
    m := make(map[string]int)
    var wg sync.WaitGroup

    for i := 0; i < 10; i++ {
        wg.Add(1)
        go func(id int) {
            defer wg.Done()
            key := fmt.Sprintf("key%d", id)
            rwmu.RLock()
            value, exists := m[key]
            rwmu.RUnlock()
            if exists {
                fmt.Printf("goroutine %d read key: %s, value: %d\n", id, key, value)
            } else {
                fmt.Printf("goroutine %d key: %s not exists\n", id, key)
            }
        }(i)
    }

    for i := 0; i < 5; i++ {
        wg.Add(1)
        go func(id int) {
            defer wg.Done()
            key := fmt.Sprintf("key%d", id)
            rwmu.Lock()
            m[key] = id
            fmt.Printf("goroutine %d inserted key: %s, value: %d\n", id, key, id)
            rwmu.Unlock()
        }(i)
    }

    for i := 0; i < 3; i++ {
        wg.Add(1)
        go func(id int) {
            defer wg.Done()
            key := fmt.Sprintf("key%d", id)
            rwmu.Lock()
            delete(m, key)
            fmt.Printf("goroutine %d deleted key: %s\n", id, key)
            rwmu.Unlock()
        }(i)
    }

    wg.Wait()
    fmt.Println("Final map:", m)
}
  1. 使用sync.Map
    • 原理:Go 1.9 引入的sync.Map是线程安全的map实现,它内部使用了更复杂的机制来实现并发安全,适合高并发读写场景。
    • 代码示例
package main

import (
    "fmt"
    "sync"
)

func main() {
    var wg sync.WaitGroup
    var m sync.Map

    for i := 0; i < 10; i++ {
        wg.Add(1)
        go func(id int) {
            defer wg.Done()
            key := fmt.Sprintf("key%d", id)
            m.Store(key, id)
            fmt.Printf("goroutine %d inserted key: %s, value: %d\n", id, key, id)
        }(i)
    }

    for i := 0; i < 5; i++ {
        wg.Add(1)
        go func(id int) {
            defer wg.Done()
            key := fmt.Sprintf("key%d", id)
            m.Delete(key)
            fmt.Printf("goroutine %d deleted key: %s\n", id, key)
        }(i)
    }

    wg.Wait()
    m.Range(func(key, value interface{}) bool {
        fmt.Printf("Final key: %v, value: %v\n", key, value)
        return true
    })
}