GO语言并发编程之互斥锁、读写锁详解
什么是互斥锁和读写锁
在并发编程中,多个 goroutine(协程)同时访问某个共享资源,容易出现数据竞争的情况,导致程序出现意想不到的结果或全面崩溃。为了解决这个问题,Go 语言提供了互斥锁(Mutex)和读写锁(RWMutex)的机制。
- 互斥锁:是一个可以被锁定和解锁的标准计数信号量。在同一时刻,只能有一个 goroutine 获取该锁,其他 goroutine 需要等待该锁被释放。通过互斥锁可以实现对临界区(被保护资源)的互斥访问,避免数据竞争。
- 读写锁:与互斥锁类似,只是它允许多个 goroutine 并发读共享资源。当一个 goroutine 获取了读锁,其他 goroutine 只能再获取读锁,而不能获取写锁。当一个 goroutine 获取了写锁,其他 goroutine 都需要等到写锁被释放后才能获取读锁或写锁。
互斥锁的使用
在Go语言中,通过使用 sync
包中的 Mutex 类型来实现互斥锁功能。Mutex 有两个方法:Lock()
和 Unlock()
,能够将访问临界区(被保护资源)的 goroutine 进行同步。
下面是一些互斥锁的使用示例:
package main
import (
"fmt"
"sync"
)
type Counter struct {
val int
mu sync.Mutex
}
func (c *Counter) Add() {
c.mu.Lock() // 加锁
defer c.mu.Unlock() // 解锁
c.val++
}
func (c *Counter) Value() int {
c.mu.Lock() // 加锁
defer c.mu.Unlock() // 解锁
return c.val
}
func main() {
c := Counter{}
var wg sync.WaitGroup
wg.Add(10000)
for i := 0; i < 10000; i++ {
go func(i int) {
defer wg.Done()
c.Add()
}(i)
}
wg.Wait()
fmt.Println(c.Value()) // 10000
}
在上述示例中,创建了一个 Counter 结构体,包含一个整型变量 val 和一个互斥锁 mu。Add() 和 Value() 方法都包含了锁定和解锁的代码。在 main() 函数中,创建了一千个 goroutine 向 Counter 的 val 变量中加一,最终输出的结果是 10000。
通过添加互斥锁,可以完美地解决并发访问资源的问题。
读写锁的使用
读写锁在实现上比互斥锁稍微复杂一些,但是在读多写少的情况下,效率更高。在Go语言中,通过使用 sync
包中的 RWMutex 类型来实现读写锁功能。RWMutex 有四个方法:RLock()
和 RUnlock()
用于管理读锁,Lock()
和 Unlock()
用于管理写锁,能够保证以下操作的安全并发进行:
- 读操作:多个 goroutine 能够同时获取读锁,读取被保护资源。
- 写操作:只允许一个 goroutine 获取写锁,防止其他 goroutine 读或写该资源。
下面是一些读写锁的使用示例:
package main
import (
"fmt"
"sync"
"time"
)
type Cache struct {
value map[string]string
rw sync.RWMutex
}
func (c *Cache) Set(key, value string) {
c.rw.Lock()
defer c.rw.Unlock()
c.value[key] = value
}
func (c *Cache) Get(key string) (string, bool) {
c.rw.RLock()
defer c.rw.RUnlock()
value, ok := c.value[key]
return value, ok
}
func main() {
cache := Cache{
value: make(map[string]string),
rw: sync.RWMutex{},
}
var wg sync.WaitGroup
wg.Add(2)
// 写操作
go func() {
defer wg.Done()
for i := 0; i < 5; i++ {
cache.Set(fmt.Sprintf("key%d", i), fmt.Sprintf("value%d", i))
time.Sleep(time.Millisecond * 500)
}
}()
// 读操作
go func() {
defer wg.Done()
for i := 0; i < 10; i++ {
if value, ok := cache.Get(fmt.Sprintf("key%d", i%5)); ok {
fmt.Printf("key:%s,value:%s\n", fmt.Sprintf("key%d", i%5), value)
} else {
fmt.Printf("key:%s,not found\n", fmt.Sprintf("key%d", i%5))
}
}
}()
wg.Wait()
}
在上述示例中,创建了一个 Cache 结构体,包含一个 map 类型的 value 变量和一个读写锁 rw。通过 Set() 方法往 Cache 的 value 中存入 key/value 数据,通过 Get() 方法从 Cache 的 value 中获取值。
在 main() 函数中,分别创建了两个 goroutine:写 goroutine 往 Cache 中写入五个 key/value,每个 key/value 的间隔为 500ms;读 goroutine 循环获取十个 key,每个 key 的间隔为 100ms。这样,就可以同时对 Cache 中的数据进行读写操作了。
总结
互斥锁和读写锁是Go语言中非常重要的并发编程工具。互斥锁可以实现对共享资源的互斥访问,读写锁可以实现对共享资源的读写分离访问,实现高并发程序的效率和性能优化。在开发过程中,需要注意锁的粒度和时机,避免死锁和性能问题。
本站文章如无特殊说明,均为本站原创,如若转载,请注明出处:GO语言并发编程之互斥锁、读写锁详解 - Python技术站