Go 语言为并发编程提供了丰富的同步机制,这些机制设计得既简洁又强大,旨在简化并确保在多线程环境(goroutine)中对共享资源的访问安全。以下是 Go 语言中主要的同步机制:
通道(Channels):
在 Go 中,通道是类型化的通信机制,用于 goroutine 之间的同步和数据交换。
通过通道发送和接收数据可以实现生产者-消费者模式、流水线处理等并发模式,并且隐式地执行了同步操作,使得在进行数据传输时能确保数据一致性。
package main
import "fmt"
func main() {
messages := make(chan string)
go func() { // 发送者 goroutine
messages <- "ping"
}()
msg := <-messages // 接收者 goroutine
fmt.Println(msg) // 输出:ping
}
互斥锁(sync.Mutex):
sync.Mutex 是最基本的互斥锁类型,它提供 Lock() 和 Unlock() 方法来保护临界区代码。
当一个 goroutine 获得了锁后,其他试图获取该锁的 goroutine 将被阻塞直到第一个 goroutine 释放锁为止。
package main
import (
"fmt"
"sync"
)
var count int
var mutex sync.Mutex
func increment() {
mutex.Lock()
count++
mutex.Unlock()
}
func main() {
var wg sync.WaitGroup
for i := 0; i < 1000; i++ {
wg.Add(1)
go func() {
increment()
wg.Done()
}()
}
wg.Wait()
fmt.Println(count) // 输出:1000
}
读写互斥锁(sync.RWMutex):
提供了比 Mutex 更细粒度的控制,支持多个读取者同时访问资源,但同一时间只允许一个写入者。
具有 RLock()(读取锁)、RUnlock()(释放读取锁)、Lock()(独占锁)和 Unlock()(释放独占锁)方法。
package main
import (
"fmt"
"sync"
)
type Counter struct {
mu sync.RWMutex
val int
}
func (c *Counter) Incr() {
c.mu.Lock()
c.val++
c.mu.Unlock()
}
func (c *Counter) Read() int {
c.mu.RLock()
defer c.mu.RUnlock()
return c.val
}
func main() {
counter := &Counter{val: 0}
for i := 0; i < 1000; i++ {
go counter.Incr()
}
var sum int
for i := 0; i < 10; i++ {
sum += counter.Read()
}
fmt.Println("Final value:", sum) // 输出大于等于1000的结果
}
WaitGroup(sync.WaitGroup):
WaitGroup 用于等待一组 goroutine 完成其工作。
使用 Add(delta int) 设置需要等待的任务数量,每个完成任务的 goroutine 都调用一次 Done() 减少计数器,而主线程或其他协调者可以通过 Wait() 来阻塞直到所有任务都完成了。
package main
import (
"fmt"
"sync"
"time"
)
func worker(id int, wg *sync.WaitGroup) {
defer wg.Done()
fmt.Printf("Worker %d started\n", id)
time.Sleep(time.Second)
fmt.Printf("Worker %d finished\n", id)
}
func main() {
var wg sync.WaitGroup
for i := 1; i <= 5; i++ {
wg.Add(1)
go worker(i, &wg)
}
wg.Wait()
fmt.Println("All workers have finished")
}
Once(sync.Once):
Once 类型保证某个动作在其生命周期内仅执行一次。
使用 Do(f func()) 方法执行给定的函数 f,即便在多个 goroutine 同时调用 Do 方法的情况下,f 函数也只会被执行一次。
package main
import (
"fmt"
"sync"
)
var once sync.Once
var message string
func setup() {
message = "Hello, World!"
}
func printMessage() {
once.Do(setup)
fmt.Println(message)
}
func main() {
printMessage()
printMessage() // 第二次调用不会重新执行 setup 函数
}
原子操作(sync/atomic 包):
提供了一系列无锁的原子操作,如原子增加、减少、交换、比较并交换等,适用于低级别的同步需求,尤其是在更新不需要复杂锁定逻辑的简单数值变量时。
package main
import (
"fmt"
"sync/atomic"
)
var count int64
func increment() {
atomic.AddInt64(&count, 1)
}
func main() {
for i := 0; i < 1000; i++ {
go increment()
}
time.Sleep(time.Second) // 确保所有goroutine完成操作
fmt.Println(atomic.LoadInt64(&count)) // 输出:1000
}
Cond(sync.Cond):
Cond 可以在满足特定条件时唤醒等待的 goroutine,允许基于条件的同步。
使用 L 字段关联一个互斥锁,然后使用 Broadcast()、Signal() 和 Wait() 方法进行条件通知和等待。
以下是一个使用 sync.Cond 的简单示例,它展示了如何在一个 goroutine 中修改共享资源状态,并通过条件变量通知其他等待该条件的 goroutine:
package main
import (
"fmt"
"sync"
)
var count int
var cond = sync.NewCond(new(sync.Mutex))
func incrementAndNotify() {
cond.L.Lock()
defer cond.L.Unlock()
for count < 5 { // 当计数未达到5时继续循环
fmt.Printf("Count is %d, waiting for condition...\n", count)
cond.Wait() // 当条件不满足时挂起当前goroutine
}
fmt.Println("Count reached 5, incrementing...")
count++
fmt.Println("Incremented count to:", count)
// 条件满足后,唤醒所有等待的goroutine
cond.Broadcast()
}
func main() {
go incrementAndNotify()
// 修改共享资源状态
cond.L.Lock()
count = 4
fmt.Println("Setting count to 4 and signaling condition.")
cond.Signal() // 唤醒一个等待的goroutine(这里可能有一个或多个在等待)
cond.L.Unlock()
time.Sleep(time.Second) // 确保goroutine有足够时间处理信号
cond.L.Lock()
if count != 5 {
fmt.Println("Final count should be 5 but it's not")
} else {
fmt.Println("Final count is correct (5)")
}
cond.L.Unlock()
}
在这个例子中,我们创建了一个全局的 sync.Cond 对象。incrementAndNotify 函数会一直等待直到计数器达到 5。当主函数设置计数器为 4 并发送一个信号时,等待条件的 goroutine 将被唤醒并执行后续逻辑。
以上同步机制构成了 Go 语言并发编程的基础工具集,它们共同作用于解决并发环境下的数据竞争、顺序依赖等问题,保证程序在多线程环境中的正确性和性能表现。