使用互斥锁实现
- 代码示例:
package main
import (
"fmt"
"sync"
)
var (
mu sync.Mutex
count int
)
func increment(wg *sync.WaitGroup) {
defer wg.Done()
mu.Lock()
count++
mu.Unlock()
}
func main() {
var wg sync.WaitGroup
numGoroutines := 10
for i := 0; i < numGoroutines; i++ {
wg.Add(1)
go increment(&wg)
}
wg.Wait()
fmt.Println("Final count:", count)
}
- 实现原理:通过
sync.Mutex
的Lock
方法来锁定共享资源,使得在同一时间只有一个goroutine可以访问共享的整数变量count
。Unlock
方法用于释放锁,允许其他goroutine获取锁并访问资源。
使用通道实现
- 代码示例:
package main
import (
"fmt"
"sync"
)
func increment(ch chan int, wg *sync.WaitGroup) {
defer wg.Done()
current := <-ch
current++
ch <- current
}
func main() {
var wg sync.WaitGroup
numGoroutines := 10
ch := make(chan int, 1)
ch <- 0
for i := 0; i < numGoroutines; i++ {
wg.Add(1)
go increment(ch, &wg)
}
go func() {
wg.Wait()
close(ch)
}()
for result := range ch {
fmt.Println("Final count:", result)
}
}
- 实现原理:使用一个带缓冲的通道
ch
来传递共享整数变量的状态。每个goroutine从通道中接收当前值,进行修改后再将新值发送回通道。
性能差异
- 互斥锁:
- 优点:在简单场景下性能较好,尤其是读操作远多于写操作时。因为读操作可以并发进行(如果使用
sync.RWMutex
的读锁),减少了锁争用。
- 缺点:写操作时会阻塞其他读写操作,可能导致性能瓶颈,特别是在高并发场景下。
- 通道:
- 优点:在高并发场景下,通道通过消息传递来避免共享资源竞争,减少锁争用,性能可能更好。
- 缺点:由于通道的发送和接收操作是阻塞的,在一些简单场景下可能会引入不必要的开销,比如频繁的通道操作。
代码复杂度差异
- 互斥锁:
- 优点:代码相对简单直观,只需在访问共享资源前后添加锁操作。
- 缺点:需要小心处理锁的粒度和死锁问题。如果锁的粒度太大,会降低并发性能;如果锁的粒度太小,又可能导致死锁。
- 通道:
- 优点:代码逻辑相对清晰,通过通道传递数据避免了共享资源的直接操作,减少了死锁风险。
- 缺点:通道的使用需要更多的设计和规划,比如通道的缓冲大小、如何正确地关闭通道等,增加了代码复杂度。