Skip to content

并发安全

  • 并发安全指的是多个 goroutine 同时访问共享资源时,不会出现数据竞争、逻辑错误或不一致的结果。

互斥锁 (Mutex)

  • 互斥锁,同一时间只允许一个 goroutine 访问资源(读 / 写互斥)。

  • lock1 会先执行,lock2 会阻塞,后续代码不会执行 因为 lock1 已获没有解锁

go
var mu sync.Mutex
var count int

func main() {
	go lock1()
	go lock2()

	time.Sleep(1 * time.Second)
	fmt.Println("count", count)
}

func lock1() {
	mu.Lock()
	count = 2
	defer mu.Unlock()
	fmt.Println("进入lock1")
	fmt.Println("count1", count)
}

func lock2() {
    // lock2 会阻塞,后续代码不会执行 因为 lock1 已获没有解锁
	mu.Lock()
	count = 3
	fmt.Println("进入lock2")
	fmt.Println("count2", count)
}
  • lock1,lock2 两个都会执行
go
var mu sync.Mutex
var count int

func main() {
	go lock1()
	go lock2()

	time.Sleep(1 * time.Second)
	fmt.Println("count", count)
}

func lock1() {
	mu.Lock()
	count = 2
	defer mu.Unlock()
	fmt.Println("进入lock1")
	fmt.Println("count1", count)
}

func lock2() {
	mu.Lock()
    defer mu.Unlock()
	count = 3
	fmt.Println("进入lock2")
	fmt.Println("count2", count)
}

读写锁 (RWMutex)

写锁

  • 写操作会阻塞所有读写。lock3会执行,lock4被阻塞不会执行。
go
// 读写锁
var rw sync.RWMutex
var count int

func main() {
	go lock3()
	go lock4()

	time.Sleep(1 * time.Second)
	fmt.Println("count", count)
}


func lock3() {
	// 写锁
	rw.Lock()
	//defer rw.Unlock()

	count = 3
	fmt.Println("进入lock3")
	fmt.Println("count3", count)
}

func lock4() {
	// 写锁
	rw.Lock()
	//defer rw.Unlock()

	count = 4
	fmt.Println("进入lock4")
	fmt.Println("count4", count)
}

读锁

  • 读操作不会阻塞其他读写操作。lock5,lock6会同时执行。
go
// 读写锁
var rw sync.RWMutex
var count int

func main() {
	go lock5()
	go lock6()

	time.Sleep(1 * time.Second)
	fmt.Println("count", count)
}


func lock5() {
	// 读锁
	rw.RLock()
	//defer rw.Unlock()

	count = 5
	fmt.Println("进入lock5")
	fmt.Println("count5", count)
}

func lock6() {
	// 读锁
	rw.RLock()
	//defer rw.Unlock()

	count = 6
	fmt.Println("进入lock6")
	fmt.Println("count6", count)
}

原子操作

  • 针对基本数据类型(如 int32、uint64 等)的简单操作,可使用原子函数避免锁的开销。
go
var counter int64

func add() {
    atomic.AddInt64(&counter, 1) // 原子自增
}

func get() int64 {
    return atomic.LoadInt64(&counter) // 原子读取
}

通道

  • 天然并发安全,无需显式锁。
go
var ch = make(chan int, 1) // 带缓冲的 channel 可减少阻塞

// 发送方
go func() {
    ch <- 100 // 数据通过 channel 传递
}()

// 接收方
go func() {
    val := <-ch // 安全获取数据
}()

等待组

  • 等待组,用于等待多个 goroutine 完成,防止主程序提前退出。
go
var wg sync.WaitGroup

func task() {
    defer wg.Done() // 任务完成时减 1
    // 业务逻辑
}

func main() {
    for i := 0; i < 5; i++ {
        wg.Add(1)
        go task()
    }
    wg.Wait() // 等待所有任务完成
}

sync.Map

  • 并发安全的映射(哈希表),属于 sync 包,专门用于读多写少的并发场景。
  • 提供了并发安全的 Store、Load、Delete、Range 等方法。
go
package main
import (
    "fmt"
    "sync"
)
var sMap sync.Map
func main() {
    // 添加元素
    sMap.Store("k1", "value1")
    sMap.Store("k2", "value")

    // 修改元素
    sMap.Store("k1", "newValue")
    // 更新元素
    sMap.Swap("k1", "newValue2")
    // 获取元素
    v1, ok := sMap.Load("k1")
    if ok {
        fmt.Println(v1)
    }
    // 遍历
    sMap.Range(func(key, value interface{}) bool {
        fmt.Println(key, value)
        return true
    })
    // 删除元素
    sMap.Delete("k2")
}

sync.Once

  • 确保某个操作只执行一次,常用于初始化操作。
  • 示例:初始化数据库连接、加载配置文件、单例模式。
go
var once sync.Once
once.Do(func() {
    // 初始化操作,只执行一次
})

常见问题

  • 死锁:多个 goroutine 等待对方释放锁,导致程序无法继续执行。
  • 活锁:多个 goroutine 不断切换执行状态,导致程序无法继续执行。
  • 饥饿:某个 goroutine 长期无法获取到锁,导致其他 goroutine 一直等待。