golang,go,博客,开源,编程

初识golang之sync包

Published on with 0 views and 0 comments

Go 的 sync 包提供了多个用于同步并发操作的工具,主要包括互斥锁(Mutex)、等待组(WaitGroup)、读写锁(RWMutex)、一次性操作(Once)、条件变量(Cond)等。这些工具使得在多协程并发环境下的共享资源访问更加安全。

下面详细介绍 sync 包中的常用类型和它们的使用。

1. sync.Mutex(互斥锁)

Mutex(互斥锁)用于控制对共享资源的访问,确保在同一时刻只有一个 goroutine 可以访问该资源。

示例

package main

import (
	"fmt"
	"sync"
)

var mu sync.Mutex
var counter int

func increment() {
	mu.Lock()         // 上锁
	defer mu.Unlock() // 解锁

	counter++
}

func main() {
	var wg sync.WaitGroup
	for i := 0; i < 1000; i++ {
		wg.Add(1)
		go func() {
			defer wg.Done()
			increment()
		}()
	}

	wg.Wait()
	fmt.Println("Counter:", counter)
}

说明

  • mu.Lock():加锁,确保当前 goroutine 可以安全地访问共享资源。
  • mu.Unlock():解锁,允许其他 goroutine 访问共享资源。
  • defer mu.Unlock() 确保即使函数中途返回,锁也会被正确释放。

2. sync.RWMutex(读写锁)

RWMutex 是一种更细粒度的锁,它允许多个 goroutine 同时读取共享资源,但当有一个 goroutine 写入资源时,其他 goroutine 无法进行读或写操作。它适用于读多写少的场景,可以提高性能。

示例

package main

import (
	"fmt"
	"sync"
)

var rwmu sync.RWMutex
var counter int

func readCounter() int {
	rwmu.RLock()         // 获取读锁
	defer rwmu.RUnlock() // 释放读锁
	return counter
}

func writeCounter(value int) {
	rwmu.Lock()         // 获取写锁
	defer rwmu.Unlock() // 释放写锁
	counter = value
}

func main() {
	var wg sync.WaitGroup
	for i := 0; i < 10; i++ {
		wg.Add(1)
		go func() {
			defer wg.Done()
			writeCounter(i)
		}()
	}

	for i := 0; i < 10; i++ {
		wg.Add(1)
		go func() {
			defer wg.Done()
			fmt.Println(readCounter())
		}()
	}

	wg.Wait()
}

说明

  • rwmu.RLock():加读锁,允许多个 goroutine 同时读取共享资源。
  • rwmu.RUnlock():释放读锁。
  • rwmu.Lock():加写锁,只有一个 goroutine 可以写入资源。
  • rwmu.Unlock():释放写锁。

3. sync.WaitGroup(等待组)

WaitGroup 用于等待一组 goroutine 执行完毕。它主要有以下三种方法:

  • Add(n int):设置等待的 goroutine 数量,通常在启动 goroutine 前调用。
  • Done():每个 goroutine 执行完后调用,表示一个 goroutine 完成。
  • Wait():阻塞,直到所有 goroutine 执行完毕。

示例

package main

import (
	"fmt"
	"sync"
)

func worker(id int, wg *sync.WaitGroup) {
	defer wg.Done()
	fmt.Printf("Worker %d started\n", id)
}

func main() {
	var wg sync.WaitGroup
	for i := 0; i < 5; i++ {
		wg.Add(1) // 每启动一个 goroutine 都要调用一次 Add
		go worker(i, &wg)
	}
	wg.Wait() // 等待所有 goroutine 执行完毕
}

说明

  • wg.Add(1):增加一个等待的 goroutine。
  • wg.Done():每个 goroutine 执行完成后调用。
  • wg.Wait():主 goroutine 会等待所有其他 goroutine 执行完毕。

4. sync.Once(一次性操作)

Once 类型用于确保某个操作仅执行一次,常用于需要进行单次初始化的场景。Once 会保证该操作只会在并发环境下执行一次,即使有多个 goroutine 同时尝试执行它。

示例

package main

import (
	"fmt"
	"sync"
)

var once sync.Once

func initOnce() {
	fmt.Println("This is only executed once.")
}

func main() {
	for i := 0; i < 5; i++ {
		go once.Do(initOnce) // 保证 initOnce 只会执行一次
	}

	// 等待 goroutine 完成
	var wg sync.WaitGroup
	wg.Add(1)
	go func() {
		defer wg.Done()
		once.Do(initOnce) // 只会执行一次
	}()
	wg.Wait()
}

说明

  • once.Do(fn):确保函数 fn 仅执行一次。

5. sync.Cond(条件变量)

Cond 类型提供了与其他 goroutine 协调的方式,类似于传统编程中的条件变量。它允许一个 goroutine 等待某个条件满足,然后通知其他 goroutine。

示例

package main

import (
	"fmt"
	"sync"
)

var mu sync.Mutex
var cond = sync.NewCond(&mu)
var ready bool

func waiter() {
	cond.L.Lock()  // 上锁
	defer cond.L.Unlock()
	for !ready {   // 检查条件
		cond.Wait() // 等待条件
	}
	fmt.Println("The condition is ready!")
}

func signaler() {
	cond.L.Lock()
	ready = true // 更新条件
	cond.Signal() // 唤醒一个等待的 goroutine
	cond.L.Unlock()
}

func main() {
	go waiter()
	go signaler()

	// 等待 goroutine 完成
	var wg sync.WaitGroup
	wg.Add(1)
	go func() {
		defer wg.Done()
		signaler()
	}()
	wg.Wait()
}

说明

  • cond.L.Lock():对条件变量所在的锁加锁。
  • cond.Wait():阻塞当前 goroutine,直到收到通知。
  • cond.Signal():通知一个等待中的 goroutine。
  • cond.Broadcast():通知所有等待中的 goroutine。

总结

Go 的 sync 包提供了多种同步机制,适用于不同的并发控制需求:

  • sync.Mutex:最常用的互斥锁,保证同一时刻只有一个 goroutine 访问共享资源。
  • sync.RWMutex:读写锁,支持多个 goroutine 同时读取,只有一个 goroutine 写入。
  • sync.WaitGroup:等待多个 goroutine 执行完毕。
  • sync.Once:确保某个操作只执行一次。
  • sync.Cond:条件变量,用于在多个 goroutine 间进行协调和通信。

根据你的并发需求,选择合适的同步工具可以帮助你写出高效、安全的并发程序。


标题:初识golang之sync包
作者:mooncakeee
地址:http://blog.dd95828.com/articles/2025/01/07/1736220407333.html
联系:scotttu@163.com