Golang并发锁使用详解:sync.Mutex与RWMutex实战
本文通过具体的众筹存钱案例,揭示Golang多协程并发访问共享数据导致的结果错误,深入解析如何使用sync.Mutex解决基本线程安全问题,并利用sync.RWMutex优化读多写少场景的性能。
package main
import (
"fmt"
"sync"
)
var wg sync.WaitGroup
func Save(income int, balance *int) {
defer wg.Done()
*balance = *balance + income
}
func main() {
balance := 0
for s := 0; s < 1000; s++ {
wg.Add(1)
go Save(1, &balance)
}
wg.Wait()
fmt.Println("当前账户余额:", balance)
}
由于Goroutine的调度顺序不确定,多个协程可能同时读取到相同的余额值(例如0),计算后依次回写。这会导致部分存款被覆盖,最终输出结果通常为随机值,而非预期的1000元。要解决此问题,必须引入并发锁机制,确保同一时刻仅有一个协程能修改该数据。
使用互斥锁保证数据安全
Go标准库中的sync.Mutex 是解决此类问题的基础工具。通过在修改变量前调用 Lock(),修改完成后调用 Unlock(),可以确保操作的原子性。
package main
import (
"fmt"
"sync"
)
var wg sync.WaitGroup
var lk sync.Mutex
func Save(income int, balance *int) {
defer wg.Done()
lk.Lock()
*balance = *balance + income
lk.Unlock()
}
func main() {
balance := 0
for s := 0; s < 1000; s++ {
wg.Add(1)
go Save(1, &balance)
}
wg.Wait()
fmt.Println("当前账户余额:", balance)
}
加入锁逻辑后,无论协程如何调度,每次执行 *balance = *balance + income 操作时,其他尝试进入的协程都会被阻塞。多次运行测试,结果均稳定为“当前账户余额: 1000”,证明了数据的一致性得到了保障。
高并发读场景下的读写锁优化
互斥锁虽然简单有效,但属于排他锁。即使协程仅读取数据而不修改,也必须等待锁释放,这在“读多写少”的业务场景中会造成严重的性能瓶颈。为此,可以使用sync.RWMutex(读写锁)进行优化。
读写锁的工作原理如下:
- **写锁**:Lock() 会阻塞所有其他的读写协程,确保写入期间没有其他并发操作。
- **读锁**:RLock() 允许其他协程继续并发读取,只有在进行写操作时才需要独占。
以下为读写锁的应用演示:
package main
import (
"fmt"
"sync"
"time"
)
var wg sync.WaitGroup
var lk sync.RWMutex
// 模拟写操作
func Save(thr int, income int, balance *int) {
defer wg.Done()
lk.Lock()
fmt.Printf("write-groutine-< %d >添加写锁\n", thr)
*balance = *balance + income
time.Sleep(time.Millisecond * 1)
fmt.Printf("write-groutine-< %d >解除写锁\n", thr)
lk.Unlock()
}
// 模拟读操作
func Show(thr int, balance *int) {
defer wg.Done()
lk.RLock()
fmt.Printf("read-groutine-< %d >开始读取数据\n", thr)
time.Sleep(time.Millisecond * 1)
fmt.Printf("read-groutine-< %d >完成读取数据\n", thr)
lk.RUnlock()
}
func main() {
balance := 0
StartTime := time.Now()
// 3次写操作
for s := 0; s < 3; s++ {
wg.Add(1)
go Save(s, 1, &balance)
}
// 10次读操作
for sh := 0; sh < 10; sh++ {
wg.Add(1)
go Show(sh, &balance)
}
wg.Wait()
fmt.Println("最终账户余额:", balance)
TimeRange := time.Since(StartTime)
fmt.Println("程序运行耗时: ", TimeRange)
}
运行结果可以观察到明显的差异:
1. 写操作严格按照顺序串行执行(一次完整的加锁、修改、解锁过程)。在写锁持有期间,后续的所有协程都被挂起。
2. 读操作几乎瞬间启动且乱序打印。只要没有协程占用写锁,所有的读取协程都可以并行获取读锁,从而极大地提升了程序的吞吐量。
以上就是Golang并发锁机制与sync.RWMutex应用的详细内容,更多关于Golang并发编程实战的资料请关注本站其它相关文章!本站声明
本文内容仅供参考,不代表本站观点。转载或引用请注明出处。如发现内容问题,请通过联系我们页面反馈。













