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

go的同步原语

Published on with 0 views and 0 comments

在 Go 语言中,常见的同步原语有以下几种:

1. sync.Mutex(互斥锁)

Mutex 是最常用的同步原语之一,用于保证同一时间只有一个 Goroutine 能够访问某个共享资源。它有两个主要方法:

  • Lock():锁定互斥量,直到它被解锁。
  • Unlock():解锁互斥量,允许其他 Goroutine 锁定它。

示例:

package main

import (
	"fmt"
	"sync"
)

var (
	mutex  sync.Mutex
	counter int
)

func increment() {
	mutex.Lock()  // 锁定互斥量
	defer mutex.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)
}

2. sync.RWMutex(读写锁)

RWMutexMutex 的一个扩展,允许多个 Goroutine 同时读取共享资源,但在写入时只有一个 Goroutine 可以访问资源。它有以下方法:

  • RLock():加读锁,多个 Goroutine 可以同时获取读锁。
  • RUnlock():释放读锁。
  • Lock():加写锁,只有一个 Goroutine 可以获取写锁。
  • Unlock():释放写锁。

示例:

package main

import (
	"fmt"
	"sync"
)

var (
	rwMutex   sync.RWMutex
	data      int
)

func readData() {
	rwMutex.RLock() // 获取读锁
	defer rwMutex.RUnlock() // 释放读锁
	fmt.Println("Reading data:", data)
}

func writeData(newData int) {
	rwMutex.Lock() // 获取写锁
	defer rwMutex.Unlock() // 释放写锁
	data = newData
	fmt.Println("Writing data:", data)
}

func main() {
	var wg sync.WaitGroup
	for i := 0; i < 5; i++ {
		wg.Add(1)
		go func() {
			defer wg.Done()
			readData()
		}()
	}
	wg.Add(1)
	go func() {
		defer wg.Done()
		writeData(42)
	}()
	wg.Wait()
}

3. sync.WaitGroup(等待组)

WaitGroup 用于等待一组 Goroutine 完成。在多个 Goroutine 并发执行时,可以使用 WaitGroup 来等待所有任务完成。

  • Add(delta int):增加等待的 Goroutine 数量。
  • Done():通知 WaitGroup 该 Goroutine 完成。
  • Wait():阻塞直到 WaitGroup 中的计数器减到 0。

示例:

package main

import (
	"fmt"
	"sync"
)

func task(wg *sync.WaitGroup, id int) {
	defer wg.Done() // 完成时减少计数
	fmt.Printf("Task %d is starting\n", id)
}

func main() {
	var wg sync.WaitGroup
	for i := 1; i <= 3; i++ {
		wg.Add(1)
		go task(&wg, i)
	}
	wg.Wait() // 等待所有任务完成
	fmt.Println("All tasks are done!")
}

4. sync.Once(一次性执行)

Once 确保某些操作只执行一次,通常用于初始化某些资源。例如,在并发环境中,可能需要保证某些初始化操作只执行一次,而不被多个 Goroutine 重复执行。

  • Do(f func()):执行 f 函数,确保只执行一次。

示例:

package main

import (
	"fmt"
	"sync"
)

var once sync.Once

func initialize() {
	fmt.Println("Initializing resources...")
}

func main() {
	for i := 0; i < 5; i++ {
		go func() {
			once.Do(initialize) // 确保只初始化一次
		}()
	}
}

5. sync/atomic(原子操作)

sync/atomic 包提供了一些原子操作,常用于多线程环境下对基本数据类型进行操作而不需要加锁。这些操作能够保证线程安全,避免使用更重的同步原语。

  • atomic.AddInt32, atomic.AddInt64: 原子加法。
  • atomic.CompareAndSwapInt32, atomic.CompareAndSwapInt64: 原子比较并交换。
  • atomic.LoadInt32, atomic.LoadInt64: 原子读取。

示例:

package main

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

var counter int32

func increment() {
	atomic.AddInt32(&counter, 1)
}

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)
}

6. channel(通道)

Go 的通道不仅是数据交换的工具,还可以用于同步操作。通过通道发送和接收数据,可以在多个 Goroutine 之间进行同步。

示例:

package main

import (
	"fmt"
)

func task(ch chan bool) {
	// 任务完成后发送信号
	fmt.Println("Task is complete")
	ch <- true
}

func main() {
	ch := make(chan bool)
	go task(ch)
	<-ch  // 阻塞,等待任务完成
	fmt.Println("Main function finished")
}

这些同步原语帮助我们在 Go 中高效、安全地处理并发编程。通过合理选择适当的同步原语,可以避免竞态条件、死锁等问题,提高程序的稳定性和性能。


标题:go的同步原语
作者:mooncakeee
地址:http://blog.dd95828.com/articles/2025/01/07/1736231359780.html
联系:scotttu@163.com