MST
星途 面试题库

面试题:Go语言sync包中互斥锁和读写锁在保证线程安全上的区别与应用场景

在Go语言中,sync包提供了互斥锁(Mutex)和读写锁(RWMutex)。请详细分析这两种锁在实现线程安全方面的不同之处,以及在实际应用场景中,何时应该选择互斥锁,何时应该选择读写锁,举例说明。
35.2万 热度难度
编程语言Go

知识考点

AI 面试

面试题答案

一键面试

互斥锁(Mutex)与读写锁(RWMutex)在实现线程安全方面的不同

  1. 互斥锁(Mutex)
    • 原理:互斥锁保证在同一时刻只有一个 goroutine 能够访问共享资源。当一个 goroutine 获取了互斥锁,其他 goroutine 必须等待直到该锁被释放。
    • 实现方式:Go 语言的 sync.Mutex 类型提供了 LockUnlock 方法。调用 Lock 方法获取锁,如果锁已被占用,则当前 goroutine 会被阻塞,直到锁可用。调用 Unlock 方法释放锁,允许其他等待的 goroutine 获取锁。
    • 特点:简单直接,适用于读写操作都可能修改共享资源的场景。但如果读操作频繁,使用互斥锁会导致性能下降,因为读操作也会被阻塞。
  2. 读写锁(RWMutex)
    • 原理:读写锁区分了读操作和写操作。允许多个 goroutine 同时进行读操作,因为读操作不会修改共享资源,不会引发数据竞争。但是,写操作必须是独占的,当有一个 goroutine 进行写操作时,其他任何读或写操作都必须等待。
    • 实现方式:Go 语言的 sync.RWMutex 类型提供了 RLockRUnlock 用于读操作,LockUnlock 用于写操作。多个 goroutine 可以同时调用 RLock 进行读操作,而调用 Lock 进行写操作时,会阻塞其他读和写操作。
    • 特点:适用于读多写少的场景,通过允许并发读操作提高了性能。但实现相对复杂,需要正确处理读锁和写锁的获取与释放顺序,避免死锁。

实际应用场景选择

  1. 选择互斥锁的场景
    • 场景:当读写操作都可能修改共享资源,或者写操作较为频繁时,应该选择互斥锁。例如,一个银行账户的转账操作,涉及到余额的修改,无论是读余额还是进行转账(写操作),都需要保证线程安全,防止数据竞争。
    • 示例代码
package main

import (
    "fmt"
    "sync"
)

type BankAccount struct {
    balance int
    mutex   sync.Mutex
}

func (a *BankAccount) Deposit(amount int) {
    a.mutex.Lock()
    a.balance += amount
    a.mutex.Unlock()
}

func (a *BankAccount) Withdraw(amount int) bool {
    a.mutex.Lock()
    defer a.mutex.Unlock()
    if a.balance >= amount {
        a.balance -= amount
        return true
    }
    return false
}

func main() {
    account := BankAccount{balance: 100}
    var wg sync.WaitGroup
    wg.Add(2)
    go func() {
        account.Deposit(50)
        wg.Done()
    }()
    go func() {
        account.Withdraw(30)
        wg.Done()
    }()
    wg.Wait()
    fmt.Println("Final balance:", account.balance)
}
  1. 选择读写锁的场景
    • 场景:当读操作远远多于写操作时,适合使用读写锁。比如一个在线文章阅读系统,大量用户会读取文章内容(读操作),而只有管理员偶尔会更新文章(写操作)。
    • 示例代码
package main

import (
    "fmt"
    "sync"
)

type Article struct {
    content string
    rwMutex sync.RWMutex
}

func (a *Article) Read() string {
    a.rwMutex.RLock()
    defer a.rwMutex.RUnlock()
    return a.content
}

func (a *Article) Write(newContent string) {
    a.rwMutex.Lock()
    a.content = newContent
    a.rwMutex.Unlock()
}

func main() {
    article := Article{content: "Initial article content"}
    var wg sync.WaitGroup
    for i := 0; i < 5; i++ {
        wg.Add(1)
        go func() {
            fmt.Println("Read content:", article.Read())
            wg.Done()
        }()
    }
    go func() {
        article.Write("Updated article content")
    }()
    wg.Wait()
    fmt.Println("Final read content:", article.Read())
}