Go语言中通过sync包实现并发安全,1. 使用sync.Mutex保护共享变量,如计数器示例防止数据竞争;2. 使用sync.RWMutex提升读多写少场景性能,允许多个读操作并发;3. 使用sync.Once确保初始化操作仅执行一次,适用于单例模式;4. 使用sync.WaitGroup等待所有goroutine完成,常用于任务协调。合理使用这些原语可避免竞态条件,提升程序稳定性与效率。

在Go语言中,并发安全通常通过 sync 包来实现。该包提供了多种同步原语,帮助开发者在多个goroutine访问共享资源时避免竞态条件。以下是几种常见的使用方式和场景。
1. 使用 sync.Mutex 保护共享变量
当多个 goroutine 同时读写同一个变量时,需要使用互斥锁(Mutex)来确保同一时间只有一个 goroutine 能访问该资源。
示例:并发安全的计数器
立即学习“go语言免费学习笔记(深入)”;
package main
<p>import (
"fmt"
"sync"
)</p><p>var counter int
var mu sync.Mutex</p><p>func increment(wg *sync.WaitGroup) {
defer wg.Done()
mu.Lock()
counter++
mu.Unlock()
}</p><p>func main() {
var wg sync.WaitGroup
for i := 0; i < 1000; i++ {
wg.Add(1)
go increment(&wg)
}
wg.Wait()
fmt.Println("Counter:", counter) // 输出: Counter: 1000
}
说明:每次对 counter 的修改都包裹在 mu.Lock() 和 mu.Unlock() 中,防止数据竞争。
2. 使用 sync.RWMutex 提高读性能
如果共享资源以读操作为主,可以使用读写锁 RWMutex。它允许多个读操作并发执行,但写操作独占锁。
示例:线程安全的配置存储
package main
<p>import (
"fmt"
"sync"
"time"
)</p><p>var config = make(map[string]string)
var rwmu sync.RWMutex</p><p>func readConfig(key string) string {
rwmu.RLock()
value := config[key]
rwmu.RUnlock()
return value
}</p><p>func writeConfig(key, value string) {
rwmu.Lock()
config[key] = value
rwmu.Unlock()
}</p><p>func main() {
go func() {
for i := 0; i < 10; i++ {
writeConfig("version", fmt.Sprintf("v%d", i))
time.Sleep(100 * time.Millisecond)
}
}()</p><pre class="brush:php;toolbar:false;"><pre class="brush:php;toolbar:false;">var wg sync.WaitGroup
for i := 0; i < 5; i++ {
wg.Add(1)
go func() {
defer wg.Done()
for j := 0; j < 5; j++ {
v := readConfig("version")
fmt.Println("Read:", v)
time.Sleep(50 * time.Millisecond)
}
}()
}
wg.Wait()
}
说明:RLock/RUnlock 用于读操作,Lock/Unlock 用于写操作,提升并发读性能。

如知笔记——支持markdown的在线笔记,支持ai智能写作、AI搜索,支持DeepseekR1满血大模型
27
查看详情
3. 使用 sync.Once 实现单次初始化
某些初始化操作只需执行一次,例如加载配置、初始化连接池等。sync.Once 可保证函数只运行一次,即使被多个goroutine调用。
示例:单例模式初始化
package main
<p>import (
"fmt"
"sync"
)</p><p>var instance *Service
var once sync.Once</p><p>type Service struct {
Data string
}</p><p>func GetService() *Service {
once.Do(func() {
instance = &Service{Data: "Initialized"}
fmt.Println("Service initialized")
})
return instance
}</p><p>func main() {
var wg sync.WaitGroup
for i := 0; i < 5; i++ {
wg.Add(1)
go func() {
defer wg.Done()
s := GetService()
fmt.Println(s.Data)
}()
}
wg.Wait()
}
输出中“Service initialized”只会打印一次。
4. 使用 sync.WaitGroup 等待 goroutine 完成
WaitGroup 用于主线程等待一组goroutine执行完毕,常用于并发任务协调。
示例:并发下载多个资源
package main
<p>import (
"fmt"
"sync"
)</p><p>func download(url string, wg *sync.WaitGroup) {
defer wg.Done()
fmt.Println("Downloading from", url)
// 模拟耗时操作
// time.Sleep(time.Second)
}</p><p>func main() {
urls := []string{
"<a href="https://www.php.cn/link/0a16224c296f72af9037875a027f94a6">https://www.php.cn/link/0a16224c296f72af9037875a027f94a6</a>",
"<a href="https://www.php.cn/link/6f942b9867c5426a14f5841ece172b18">https://www.php.cn/link/6f942b9867c5426a14f5841ece172b18</a>",
"<a href="https://www.php.cn/link/909c71100210781d37a568c5fc14e627">https://www.php.cn/link/909c71100210781d37a568c5fc14e627</a>",
}</p><pre class="brush:php;toolbar:false;"><pre class="brush:php;toolbar:false;">var wg sync.WaitGroup
for _, url := range urls {
wg.Add(1)
go download(url, &wg)
}
wg.Wait()
fmt.Println("All downloads completed")
}
说明:每个 goroutine 开始前调用 Add(1),结束后调用 Done(),主线程通过 Wait() 阻塞直到全部完成。
基本上就这些常见用法。合理使用 sync 包中的工具,能有效避免数据竞争,实现安全高效的并发编程。注意不要过度加锁,避免死锁或性能下降。
大家都在看:
Go Datastore Put 操作中的“无效实体类型”错误解析
Go语言并发执行外部命令:构建高效协程池的最佳实践
Go语言中带有互斥锁的结构体如何安全地实现JSON序列化
Go语言中向已关闭的Tar归档文件追加内容的实现方法
































暂无评论内容