- 使用Mutex:
- 原理:Mutex(互斥锁)是一种简单的同步原语,它在同一时间只允许一个Goroutine访问共享数据。当一个Goroutine获取了Mutex,其他Goroutine必须等待直到该Goroutine释放Mutex。
- 示例代码:
package main
import (
"fmt"
"sync"
)
type SharedData struct {
data int
mu sync.Mutex
}
func (sd *SharedData) increment(wg *sync.WaitGroup) {
defer wg.Done()
sd.mu.Lock()
sd.data++
sd.mu.Unlock()
}
func main() {
var wg sync.WaitGroup
shared := SharedData{}
numGoroutines := 10
for i := 0; i < numGoroutines; i++ {
wg.Add(1)
go shared.increment(&wg)
}
wg.Wait()
fmt.Println("Final data:", shared.data)
}
- 使用Channel:
- 原理:Channel可以用于在Goroutine之间传递数据,并且它本身是线程安全的。通过将对共享数据的操作封装在发送和接收操作中,可以避免竞态条件。
- 示例代码:
package main
import (
"fmt"
"sync"
)
type SharedData struct {
data int
}
func main() {
var wg sync.WaitGroup
shared := SharedData{}
numGoroutines := 10
ch := make(chan struct{})
for i := 0; i < numGoroutines; i++ {
wg.Add(1)
go func() {
defer wg.Done()
<-ch
shared.data++
ch <- struct{}{}
}()
}
ch <- struct{}{}
wg.Wait()
close(ch)
fmt.Println("Final data:", shared.data)
}
- 设计同步机制的一般步骤:
- 确定共享数据结构:首先明确哪些数据需要在多个Goroutine之间共享,例如上述示例中的
SharedData
结构体。
- 选择合适的同步原语:
- 如果只是简单的读写操作,Mutex通常是一个很好的选择,因为它简单直接,适用于大多数情况。
- 如果需要在Goroutine之间传递数据并同步操作,Channel可能更合适。
- 封装操作:将对共享数据的操作封装在函数中,在函数内部使用同步原语来确保数据一致性。例如,在
SharedData
结构体的increment
方法中使用Mutex来保护对data
的修改。
- 测试和验证:使用Go语言内置的
go test -race
命令来检测是否存在竞态条件,确保同步机制的正确性。