Go语言中的sync
包提供了丰富的同步原语,用于解决并发场景下的数据竞争问题。这些工具可以确保在多线程环境中对共享资源的正确访问,避免因竞态条件导致的程序错误。下面我们将详细解析sync
包中常见的同步原语及其使用方法。
Mutex
是Go语言中最基本的同步原语之一,用于保护临界区代码,防止多个goroutine同时访问共享资源。
package main
import (
"fmt"
"sync"
)
func main() {
var mu sync.Mutex
counter := 0
var wg sync.WaitGroup
for i := 0; i < 1000; i++ {
wg.Add(1)
go func() {
defer wg.Done()
mu.Lock()
counter++
mu.Unlock()
}()
}
wg.Wait()
fmt.Println("Counter:", counter) // 输出应为1000
}
在上述代码中,mutex.Lock()
和mutex.Unlock()
分别用于锁定和释放锁,确保每次只有一个goroutine能够修改counter
变量。
当一个资源需要频繁读取而较少写入时,使用RWMutex
会比Mutex
更高效。RWMutex
允许多个goroutine同时读取资源,但只允许一个goroutine写入。
package main
import (
"fmt"
"sync"
)
func main() {
var rwmu sync.RWMutex
data := make(map[string]int)
var wg sync.WaitGroup
for i := 0; i < 100; i++ {
wg.Add(1)
go func(i int) {
defer wg.Done()
rwmu.RLock()
value, exists := data["key"]
if !exists {
rwmu.RUnlock()
rwmu.Lock()
defer rwmu.Unlock()
data["key"] = i
} else {
rwmu.RUnlock()
}
}(i)
}
wg.Wait()
fmt.Println("Data:", data)
}
在此示例中,我们利用了RWMutex
来实现一种懒加载模式,即只有在数据不存在时才进行写入操作。
Once
确保某个操作仅执行一次,即使有多个goroutine同时调用。
package main
import (
"fmt"
"sync"
)
var once sync.Once
var data string
func initialize() {
data = "initialized"
}
func main() {
var wg sync.WaitGroup
for i := 0; i < 10; i++ {
wg.Add(1)
go func() {
defer wg.Done()
once.Do(initialize)
fmt.Println(data)
}()
}
wg.Wait()
}
无论多少个goroutine调用once.Do()
,initialize
函数只会被执行一次。
WaitGroup
用于等待一组goroutine完成工作后再继续执行后续逻辑。
package main
import (
"fmt"
"sync"
"time"
)
func worker(id int, wg *sync.WaitGroup) {
defer wg.Done()
fmt.Printf("Worker %d starting\n", id)
time.Sleep(time.Second)
fmt.Printf("Worker %d done\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 done")
}
此代码展示了如何使用WaitGroup
协调主goroutine与子goroutine之间的执行顺序。
sync.Map
是一个并发安全的map结构,适合在高并发环境下替代标准的Go map。
package main
import (
"fmt"
"sync"
)
func main() {
var m sync.Map
m.Store("key1", "value1")
m.Store("key2", "value2")
if val, ok := m.Load("key1"); ok {
fmt.Println("Key1 Value:", val)
}
m.Delete("key2")
}
sync.Map
提供了Store
、Load
和Delete
等方法来操作键值对,且保证线程安全。