MST

星途 面试题库

面试题:Go 扩展原语之 sync 包的深入理解

在 Go 语言的 sync 包中,有多种扩展原语如 Mutex、RWMutex、WaitGroup 等。请详细说明 Mutex 和 RWMutex 的适用场景有何不同,并举例说明如何在一个多 goroutine 读写共享数据的场景中合理使用 RWMutex 以提高性能。
32.1万 热度难度
编程语言Go

知识考点

AI 面试

面试题答案

一键面试

Mutex 和 RWMutex 适用场景的不同

  1. Mutex(互斥锁)
    • 适用场景:当需要保证共享资源在同一时刻只有一个 goroutine 可以访问时使用。它适用于读写操作都需要独占访问共享资源的场景,比如对共享资源的写入操作,或者对共享资源进行复杂修改(涉及多个步骤且需要保证原子性)的操作。因为只要有 goroutine 持有 Mutex,其他所有 goroutine 都不能访问共享资源,无论它们是读还是写。
    • 示例:假设我们有一个银行账户结构体,里面有余额字段,当进行转账操作时,需要修改余额,这个操作必须是原子的,以防止并发修改导致数据不一致。
package main

import (
    "fmt"
    "sync"
)

type BankAccount struct {
    balance int
    mutex   sync.Mutex
}

func (ba *BankAccount) Transfer(amount int) {
    ba.mutex.Lock()
    defer ba.mutex.Unlock()
    ba.balance += amount
}

func main() {
    var wg sync.WaitGroup
    account := BankAccount{balance: 100}
    for i := 0; i < 10; i++ {
        wg.Add(1)
        go func() {
            defer wg.Done()
            account.Transfer(10)
        }()
    }
    wg.Wait()
    fmt.Println("Final balance:", account.balance)
}
  1. RWMutex(读写互斥锁)
    • 适用场景:适用于读多写少的场景。它允许多个 goroutine 同时进行读操作,因为读操作不会修改共享资源,不会导致数据不一致。但是当有 goroutine 进行写操作时,其他读和写操作都必须等待,直到写操作完成。这在共享数据大部分时间被读取,偶尔被写入的场景下能显著提高性能。
    • 示例:假设有一个缓存,里面存储了一些配置信息,大部分时间是被读取,偶尔会更新配置。
package main

import (
    "fmt"
    "sync"
)

type ConfigCache struct {
    data    map[string]string
    rwMutex sync.RWMutex
}

func (cc *ConfigCache) Read(key string) string {
    cc.rwMutex.RLock()
    defer cc.rwMutex.RUnlock()
    return cc.data[key]
}

func (cc *ConfigCache) Write(key, value string) {
    cc.rwMutex.Lock()
    defer cc.rwMutex.Unlock()
    if cc.data == nil {
        cc.data = make(map[string]string)
    }
    cc.data[key] = value
}

func main() {
    var wg sync.WaitGroup
    cache := ConfigCache{}
    // 模拟多个读操作
    for i := 0; i < 10; i++ {
        wg.Add(1)
        go func() {
            defer wg.Done()
            value := cache.Read("someKey")
            fmt.Println("Read value:", value)
        }()
    }
    // 模拟一个写操作
    wg.Add(1)
    go func() {
        defer wg.Done()
        cache.Write("someKey", "someValue")
    }()
    wg.Wait()
}

在多 goroutine 读写共享数据场景中合理使用 RWMutex 提高性能

  1. 分析场景:假设我们有一个博客文章存储系统,文章内容被多个用户(多个 goroutine)读取,同时管理员偶尔会更新文章内容。这种读多写少的场景非常适合使用 RWMutex。
  2. 代码实现
package main

import (
    "fmt"
    "sync"
)

type BlogArticle struct {
    content string
    rwMutex sync.RWMutex
}

func (ba *BlogArticle) ReadContent() string {
    ba.rwMutex.RLock()
    defer ba.rwMutex.RUnlock()
    return ba.content
}

func (ba *BlogArticle) UpdateContent(newContent string) {
    ba.rwMutex.Lock()
    defer ba.rwMutex.Unlock()
    ba.content = newContent
}

func main() {
    var wg sync.WaitGroup
    article := BlogArticle{content: "Initial content"}
    // 模拟多个用户读取文章
    for i := 0; i < 20; i++ {
        wg.Add(1)
        go func() {
            defer wg.Done()
            content := article.ReadContent()
            fmt.Println("Read content:", content)
        }()
    }
    // 模拟管理员更新文章
    wg.Add(1)
    go func() {
        defer wg.Done()
        article.UpdateContent("Updated content")
    }()
    wg.Wait()
}

在上述代码中,ReadContent 方法使用 RLock 允许并发读,UpdateContent 方法使用 Lock 保证写操作的独占性,从而在这种读多写少的场景下提高了性能。