什么是共享内存并发?
在传统的并发模型中,多个线程或 Goroutine 共享同一块内存空间。这意味着它们可以直接访问和修改相同的数据,而无需进行显式的数据传递。这种方式可以提高效率,但也带来了数据竞争和同步问题。
Go 语言中的共享内存并发
在 Go 语言中,Goroutine 是轻量级的并发执行单元。多个 Goroutine 可以并发地访问和修改共享内存。
示例代码:
package main
import (
"fmt"
"sync"
)
var counter int = 0
var wg sync.WaitGroup //`sync.WaitGroup`是一个用于等待一组goroutine完成的同步机制
func increment() {
defer wg.Done() //在函数返回前调用Done()
for i := 0; i < 5000; i++ {
counter++ // 潜在的数据竞争
}
}
func main() {
wg.Add(2) //告诉`WaitGroup`你将要启动2个goroutine
go increment()
go increment()
wg.Wait() //在所有goroutine启动后,使用`Wait()`方法阻塞当前goroutine,直到所有通过`Add()`添加的任务都通过调用`Done()`完成
fmt.Println("Counter:", counter)
}
问题:数据竞争
上面的代码看似简单,但存在严重的数据竞争问题。由于多个 Goroutine 同时修改 counter
变量,导致最终结果可能不正确。
如何解决数据竞争?
Go 语言提供了多种机制来解决共享内存并发中的数据竞争问题:
-
互斥锁(Mutex): 互斥锁是最常用的同步机制。它可以确保在同一时刻只有一个 Goroutine 可以访问共享资源。
package main import ( "fmt" "runtime" "sync" ) var counter int = 0 var wg sync.WaitGroup var mu sync.Mutex // 互斥锁 func increment() { defer wg.Done() for i := 0; i < 1000; i++ { mu.Lock() // 加锁 counter++ mu.Unlock() // 解锁 } } func main() { wg.Add(2) go increment() go increment() wg.Wait() fmt.Println("Counter:", counter) }
-
读写锁(RWMutex): 读写锁允许多个 Goroutine 同时读取共享资源,但只允许一个 Goroutine 写入共享资源。这在读多写少的场景下可以提高性能。
package main import ( "fmt" "runtime" "sync" ) var counter int = 0 var wg sync.WaitGroup var rwmu sync.RWMutex // 读写锁 func readCounter() { defer wg.Done() rwmu.RLock() // 读锁 fmt.Println("Counter:", counter) rwmu.RUnlock() // 释放读锁 } func increment() { defer wg.Done() rwmu.Lock() // 写锁 counter++ rwmu.Unlock() // 释放写锁 } func main() { wg.Add(3) go readCounter() go increment() go readCounter() wg.Wait() fmt.Println("Final Counter:", counter) }
-
原子操作(Atomic Operations): 原子操作是不可分割的操作,可以保证在多线程环境下对共享变量的访问是安全的。
package main import ( "fmt" "runtime" "sync" "sync/atomic" ) var counter int64 = 0 var wg sync.WaitGroup func increment() { defer wg.Done() for i := 0; i < 5000; i++ { atomic.AddInt64(&counter, 1) // 原子操作 } } func main() { wg.Add(2) go increment() go increment() wg.Wait() fmt.Println("Counter:", counter) }
总结
共享内存并发是 Go 语言中实现高性能并发应用的重要技术。通过理解其原理、掌握其用法,并避免常见的陷阱,你可以编写出高效、可靠的并发程序。
感谢阅读!如果你觉得这篇文章对你有帮助,请分享给你的朋友们,让更多的人一起学习Go语言!