MST

星途 面试题库

面试题:Go语言atomic包的内存同步原语与sync.Mutex的比较与选择

在Go语言并发编程中,atomic包提供的内存同步原语和sync.Mutex都能实现一定程度的同步控制。请深入分析两者在性能、适用场景等方面的差异,并举出具体案例说明在何种情况下应优先选择atomic原语,何种情况下应优先选择sync.Mutex。
44.4万 热度难度
编程语言Go

知识考点

AI 面试

面试题答案

一键面试

性能差异

  1. atomic包
    • 原子操作通常非常轻量级,因为它们是由底层硬件指令支持的。例如在简单的计数器场景下,对一个整数类型进行原子加操作,不需要像锁那样涉及复杂的上下文切换等开销。
    • 适用于非常简单的、对单个变量的读写操作,在高并发下性能优势明显,因为它不会像锁那样阻塞其他goroutine的运行,只要硬件支持原子操作,就能快速完成。
  2. sync.Mutex
    • 互斥锁相对来说开销较大。当一个goroutine获取锁时,如果锁已被占用,该goroutine会被阻塞并进入睡眠状态,直到锁被释放。这涉及到操作系统的调度等开销,在高并发场景下频繁的加锁解锁操作可能会导致性能瓶颈。

适用场景差异

  1. atomic包
    • 适用场景:主要用于对单个共享变量进行简单的、无逻辑依赖的读写操作。例如,在统计系统中对全局计数器的增减操作,或者在并发环境下对某个标志位的原子读写。
    • 示例:实现一个简单的全局计数器。
package main

import (
    "fmt"
    "sync"
    "sync/atomic"
)

func main() {
    var count int64
    var wg sync.WaitGroup
    for i := 0; i < 10; i++ {
        wg.Add(1)
        go func() {
            defer wg.Done()
            for j := 0; j < 1000; j++ {
                atomic.AddInt64(&count, 1)
            }
        }()
    }
    wg.Wait()
    fmt.Println("Final count:", count)
}
  1. sync.Mutex
    • 适用场景:适用于对多个共享变量的读写操作,或者对共享资源进行复杂逻辑操作时,需要保证操作的原子性和一致性。例如,在实现一个并发安全的链表或者映射(map)时,因为涉及多个数据结构的读写和修改,使用互斥锁可以确保数据的一致性。
    • 示例:实现一个并发安全的map。
package main

import (
    "fmt"
    "sync"
)

type SafeMap struct {
    mu sync.Mutex
    data map[string]int
}

func (sm *SafeMap) Set(key string, value int) {
    sm.mu.Lock()
    defer sm.mu.Unlock()
    if sm.data == nil {
        sm.data = make(map[string]int)
    }
    sm.data[key] = value
}

func (sm *SafeMap) Get(key string) (int, bool) {
    sm.mu.Lock()
    defer sm.mu.Unlock()
    if sm.data == nil {
        return 0, false
    }
    value, exists := sm.data[key]
    return value, exists
}

func main() {
    sm := SafeMap{}
    var wg sync.WaitGroup
    wg.Add(2)
    go func() {
        defer wg.Done()
        sm.Set("key1", 100)
    }()
    go func() {
        defer wg.Done()
        value, exists := sm.Get("key1")
        fmt.Println("Value:", value, "Exists:", exists)
    }()
    wg.Wait()
}

总结

  • 优先选择atomic原语的情况:当只涉及对单个简单类型(如int、int64等)的共享变量进行简单的读写操作,并且需要极高的并发性能时,优先选择atomic包提供的原语。
  • 优先选择sync.Mutex的情况:当涉及多个共享变量的读写操作,或者对共享资源的操作有复杂逻辑,需要保证数据一致性和操作原子性时,优先选择sync.Mutex。