互斥锁与读写互斥锁的妙用

简介: 互斥锁与读写互斥锁的妙用

概述

在并发编程中,控制共享资源的访问是至关重要的。

Go 语言提供了两种主要的互斥锁,即 sync.Mutex(互斥锁)和 sync.RWMutex(读写互斥锁)。

本文将讨论这两种锁的使用方式、原理和适用场景,并通过实例代码演示它们在并发环境中的正确应用。


 

1. sync.Mutex(互斥锁)

1.1 互斥锁基本使用

互斥锁用于保护共享资源,确保同一时刻只有一个 Goroutine 可以访问。

下面是一个简单的示例


package main
import (  "fmt"  "sync"  "time")
var counter intvar mutex sync.Mutex
func main() {  var wg sync.WaitGroup
  for i := 0; i < 3; i++ {    wg.Add(1)    go incrementCounter(i, &wg)  }
  wg.Wait()  fmt.Printf("Final Counter: %d\n", counter)}
func incrementCounter(id int, wg *sync.WaitGroup) {  defer wg.Done()
  for i := 0; i < 5; i++ {    mutex.Lock()    counter++    fmt.Printf("Goroutine %d: Counter = %d\n", id, counter)    mutex.Unlock()    time.Sleep(100 * time.Millisecond)  }}

在这个用例中,使用 sync.Mutexcounter 变量进行了保护。

每次修改 counter 前,通过 Lock 方法锁定互斥锁,修改完成后使用 Unlock 方法释放锁。

这样可以确保多个 Goroutine 同时访问时不会发生数据竞争。

1.2 避免死锁

在使用互斥锁时,要特别注意避免死锁。

例如,如果一个 Goroutine 在锁定互斥锁后忘记释放锁,那么其他 Goroutine 将永远无法获取该锁。

下面是一个简单的死锁示例


package main
import (  "sync"  "time")
var mutex sync.Mutex
func main() {  var wg sync.WaitGroup
  wg.Add(1)  go deadlockExample(&wg)
  // 此处故意不调用 wg.Done(),导致死锁
  wg.Wait()}
func deadlockExample(wg *sync.WaitGroup) {  defer wg.Done()
  mutex.Lock()  defer mutex.Unlock()
  // 此处故意不释放锁,导致死锁  time.Sleep(2 * time.Second)}

在这个示例中,deadlockExample Goroutine 获取了互斥锁,

并在 defer 语句中注册了 Unlock 操作,但由于未调用 wg.Done()

导致 main Goroutine 永远无法结束,形成死锁。


 

2. sync.RWMutex(读写互斥锁)

2.1 读写互斥锁基本使用

读写互斥锁相比于互斥锁,更加灵活,允许多个 Goroutine 同时获取读锁,但只允许一个 Goroutine 获取写锁。

用一个简单的示例要演示


package main
import (  "fmt"  "sync"  "time")
var data map[string]stringvar rwMutex sync.RWMutex
func main() {  data = make(map[string]string)
  var wg sync.WaitGroup
  for i := 0; i < 3; i++ {    wg.Add(1)    go readData(i, &wg)  }
  for i := 0; i < 2; i++ {    wg.Add(1)    go writeData(i, &wg)  }
  wg.Wait()}
func readData(id int, wg *sync.WaitGroup) {  defer wg.Done()
  rwMutex.RLock()  defer rwMutex.RUnlock()
  fmt.Printf("Goroutine %d reading data: %v\n", id, data)    time.Sleep(500 * time.Millisecond)}
func writeData(id int, wg *sync.WaitGroup) {  defer wg.Done()
  rwMutex.Lock()  defer rwMutex.Unlock()
  key := fmt.Sprintf("key%d", id)  value := fmt.Sprintf("value%d", id)
  data[key] = value  fmt.Printf("Goroutine %d writing data: %v\n", id, data)  time.Sleep(500 * time.Millisecond)}

在这个示例中,用 sync.RWMutexdata 进行读写保护。

多个 Goroutine 可以同时获取读锁,但只有一个 Goroutine 能够获取写锁。

这样可以在读多写少的场景中提高并发性能。

2.2 避免写锁饥饿

写锁饥饿是指当有读锁持有时,写锁一直无法获取的情况。

要避免写锁饥饿,应该尽量减小读操作的临界区,避免长时间占用读锁。

下面是一个示例演示



package main
import (  "fmt"  "sync"  "time")
var data map[string]stringvar rwMutex sync.RWMutex
func main() {  data = make(map[string]string)
  var wg sync.WaitGroup
  for i := 0; i < 3; i++ {    wg.Add(1)    go readData(i, &wg)  }    //  等待读锁获取  time.Sleep(100 * time.Millisecond)
  wg.Add(1)  go writeData(1, &wg)
  wg.Wait()}
func readData(id int, wg *sync.WaitGroup) {  defer wg.Done()
  rwMutex.RLock()
  defer rwMutex.RUnlock()
  fmt.Printf("Goroutine %d reading data: %v\n", id, data)  time.Sleep(500 * time.Millisecond)}
func writeData(id int, wg *sync.WaitGroup) {  defer wg.Done()
  rwMutex.Lock()    defer rwMutex.Unlock()
  key := fmt.Sprintf("key%d", id)  value := fmt.Sprintf("value%d", id)
  data[key] = value  fmt.Printf("Goroutine %d writing data: %v\n", id, data)  time.Sleep(500 * time.Millisecond)}

在这个示例中,让三个 Goroutine 同时获取读锁,然后等待一段时间后再尝试获取写锁。

这样可以模拟写锁饥饿的情况。


 

总结

通过本文的详细讲解和实例演示,了解了 Go 语言中互斥锁和读写互斥锁的使用方式、原理以及注意事项。

在并发编程中,选择合适的锁机制是确保程序正确性和性能的关键一步。

通过灵活运用 sync.Mutexsync.RWMutex,可以更好地处理共享资源的并发访问,提高程序的健壮性和性能。

目录
相关文章
|
Cloud Native Go C语言
C 语言的 互斥锁、自旋锁、原子操作
C 语言的 互斥锁、自旋锁、原子操作
|
3月前
|
缓存 数据库
读写锁和互斥锁的区别
【10月更文挑战第6天】
116 1
|
3月前
|
运维 API 计算机视觉
深度解密协程锁、信号量以及线程锁的实现原理
深度解密协程锁、信号量以及线程锁的实现原理
56 2
|
8月前
|
Linux API C++
c++多线程——互斥锁
c++多线程——互斥锁
|
API 调度 C语言
互斥锁,自旋锁,原子操作的原理,区别和实现
v互斥锁,自旋锁,原子操作的原理,区别和实现
167 0
|
8月前
多线程并发锁的方案—互斥锁
多线程并发锁的方案—互斥锁
|
8月前
|
编译器 C++
vs2017下原子操作和互斥锁性能比较
vs2017下原子操作和互斥锁性能比较
|
算法
互斥锁原理
互斥锁原理
104 0
|
调度 C++
如何使用C++11原子操作实现自旋锁
C++自旋锁是一种低层次的同步原语,用于保护共享资源的访问。自旋锁是一种轻量级的锁,适用于短时间的资源锁定。
241 1
|
Linux
一文读懂Linux多线程中互斥锁、读写锁、自旋锁、条件变量、信号量
Hello、Hello大家好,我是木荣,今天我们继续来聊一聊Linux中多线程编程中的重要知识点,详细谈谈多线程中同步和互斥机制。
8597 1
一文读懂Linux多线程中互斥锁、读写锁、自旋锁、条件变量、信号量