
本文旨在阐述在 Go 语言并发环境下使用 Map 的正确姿势。重点讲解在读写并发的场景下,如何保证 Map 的数据安全,以及如何通过互斥锁(Mutex)来实现并发安全的 Map 访问。我们将通过示例代码和注意事项,帮助你更好地理解和应用并发安全的 Map。
在 Go 语言中,内置的 map 类型并非线程安全。这意味着,如果在多个 goroutine 中同时读写同一个 map,可能会导致数据竞争,进而引发程序崩溃或其他未定义行为。
以下几种情况需要特别注意:
最常用的方法是使用 sync.Mutex 来保护 map 的读写操作。 sync.RWMutex 提供了更细粒度的控制,允许并发读取,但仍然需要互斥写入。
package main
import (
"fmt"
"sync"
"time"
)
type ConcurrentMap struct {
sync.Mutex
data map[string]int
}
func NewConcurrentMap() *ConcurrentMap {
return &ConcurrentMap{
data: make(map[string]int),
}
}
func (m *ConcurrentMap) Set(key string, value int) {
m.Lock()
defer m.Unlock()
m.data[key] = value
}
func (m *ConcurrentMap) Get(key string) (int, bool) {
m.Lock()
defer m.Unlock()
val, ok := m.data[key]
return val, ok
}
func (m *ConcurrentMap) Delete(key string) {
m.Lock()
defer m.Unlock()
delete(m.data, key)
}
func main() {
cmap := NewConcurrentMap()
var wg sync.WaitGroup
// 启动多个 goroutine 并发写入
for i := 0; i < 10; i++ {
wg.Add(1)
go func(i int) {
defer wg.Done()
key := fmt.Sprintf("key-%d", i)
cmap.Set(key, i*10)
time.Sleep(time.Millisecond * 10) // 模拟一些耗时操作
}(i)
}
// 启动多个 goroutine 并发读取
for i := 0; i < 5; i++ {
wg.Add(1)
go func(i int) {
defer wg.Done()
key := fmt.Sprintf("key-%d", i)
val, ok := cmap.Get(key)
if ok {
fmt.Printf("Goroutine %d: key=%s, value=%d\n", i, key, val)
} else {
fmt.Printf("Goroutine %d: key=%s not found\n", i, key)
}
time.Sleep(time.Millisecond * 5) // 模拟一些耗时操作
}(i)
}
wg.Wait() // 等待所有 goroutine 完成
fmt.Println("Done.")
}代码解释:
package main
import (
"fmt"
"sync"
"time"
)
type ConcurrentMap struct {
sync.RWMutex
data map[string]int
}
func NewConcurrentMap() *ConcurrentMap {
return &ConcurrentMap{
data: make(map[string]int),
}
}
func (m *ConcurrentMap) Set(key string, value int) {
m.Lock() // 使用写锁
defer m.Unlock()
m.data[key] = value
}
func (m *ConcurrentMap) Get(key string) (int, bool) {
m.RLock() // 使用读锁
defer m.RUnlock()
val, ok := m.data[key]
return val, ok
}
func (m *ConcurrentMap) Delete(key string) {
m.Lock() // 使用写锁
defer m.Unlock()
delete(m.data, key)
}
func main() {
cmap := NewConcurrentMap()
var wg sync.WaitGroup
// 启动多个 goroutine 并发写入
for i := 0; i < 3; i++ {
wg.Add(1)
go func(i int) {
defer wg.Done()
key := fmt.Sprintf("key-%d", i)
cmap.Set(key, i*10)
time.Sleep(time.Millisecond * 10) // 模拟一些耗时操作
}(i)
}
// 启动多个 goroutine 并发读取
for i := 0; i < 5; i++ {
wg.Add(1)
go func(i int) {
defer wg.Done()
key := fmt.Sprintf("key-%d", i)
val, ok := cmap.Get(key)
if ok {
fmt.Printf("Goroutine %d: key=%s, value=%d\n", i, key, val)
} else {
fmt.Printf("Goroutine %d: key=%s not found\n", i, key)
}
time.Sleep(time.Millisecond * 5) // 模拟一些耗时操作
}(i)
}
wg.Wait() // 等待所有 goroutine 完成
fmt.Println("Done.")
}代码解释:
Go 1.9 引入了 sync.Map 类型,它是一种并发安全的 map 实现,无需显式加锁。 sync.Map 内部使用了更复杂的机制来减少锁的竞争,在高并发场景下可能比使用 sync.Mutex 的 map 性能更好。
package main
import (
"fmt"
"sync"
"time"
)
func main() {
var m sync.Map
var wg sync.WaitGroup
// 启动多个 goroutine 并发写入
for i := 0; i < 10; i++ {
wg.Add(1)
go func(i int) {
defer wg.Done()
key := fmt.Sprintf("key-%d", i)
m.Store(key, i*10)
time.Sleep(time.Millisecond * 10) // 模拟一些耗时操作
}(i)
}
// 启动多个 goroutine 并发读取
for i := 0; i < 5; i++ {
wg.Add(1)
go func(i int) {
defer wg.Done()
key := fmt.Sprintf("key-%d", i)
val, ok := m.Load(key)
if ok {
fmt.Printf("Goroutine %d: key=%s, value=%v\n", i, key, val)
} else {
fmt.Printf("Goroutine %d: key=%s not found\n", i, key)
}
time.Sleep(time.Millisecond * 5) // 模拟一些耗时操作
}(i)
}
wg.Wait() // 等待所有 goroutine 完成
fmt.Println("Done.")
}代码解释:
在 Go 语言并发编程中,确保 map 的并发安全至关重要。 可以使用 sync.Mutex 或 sync.RWMutex 来保护 map 的读写操作,也可以使用 Go 1.9 引入的 sync.Map 类型。 选择哪种方法取决于具体的应用场景和性能需求。 务必注意锁的粒度、死锁避免和性能考量,编写健壮且高效的并发程序。
以上就是Go 并发安全 Map 使用指南的详细内容,更多请关注php中文网其它相关文章!
每个人都需要一台速度更快、更稳定的 PC。随着时间的推移,垃圾文件、旧注册表数据和不必要的后台进程会占用资源并降低性能。幸运的是,许多工具可以让 Windows 保持平稳运行。
Copyright 2014-2025 https://www.php.cn/ All Rights Reserved | php.cn | 湘ICP备2023035733号