MST

星途 面试题库

面试题:Go并发编程中,互斥锁与通道在简单数据保护场景下的选择差异

假设你有一个简单的场景,多个goroutine需要读写一个共享的整数变量。请阐述在这种情况下,使用互斥锁和通道分别应该如何实现,并且分析两者在性能、代码复杂度等方面的差异。
38.1万 热度难度
编程语言Go

知识考点

AI 面试

面试题答案

一键面试

使用互斥锁实现

  1. 代码示例
package main

import (
    "fmt"
    "sync"
)

var (
    mu    sync.Mutex
    count int
)

func increment(wg *sync.WaitGroup) {
    defer wg.Done()
    mu.Lock()
    count++
    mu.Unlock()
}

func main() {
    var wg sync.WaitGroup
    numGoroutines := 10
    for i := 0; i < numGoroutines; i++ {
        wg.Add(1)
        go increment(&wg)
    }
    wg.Wait()
    fmt.Println("Final count:", count)
}
  1. 实现原理:通过sync.MutexLock方法来锁定共享资源,使得在同一时间只有一个goroutine可以访问共享的整数变量countUnlock方法用于释放锁,允许其他goroutine获取锁并访问资源。

使用通道实现

  1. 代码示例
package main

import (
    "fmt"
    "sync"
)

func increment(ch chan int, wg *sync.WaitGroup) {
    defer wg.Done()
    current := <-ch
    current++
    ch <- current
}

func main() {
    var wg sync.WaitGroup
    numGoroutines := 10
    ch := make(chan int, 1)
    ch <- 0
    for i := 0; i < numGoroutines; i++ {
        wg.Add(1)
        go increment(ch, &wg)
    }
    go func() {
        wg.Wait()
        close(ch)
    }()
    for result := range ch {
        fmt.Println("Final count:", result)
    }
}
  1. 实现原理:使用一个带缓冲的通道ch来传递共享整数变量的状态。每个goroutine从通道中接收当前值,进行修改后再将新值发送回通道。

性能差异

  1. 互斥锁
    • 优点:在简单场景下性能较好,尤其是读操作远多于写操作时。因为读操作可以并发进行(如果使用sync.RWMutex的读锁),减少了锁争用。
    • 缺点:写操作时会阻塞其他读写操作,可能导致性能瓶颈,特别是在高并发场景下。
  2. 通道
    • 优点:在高并发场景下,通道通过消息传递来避免共享资源竞争,减少锁争用,性能可能更好。
    • 缺点:由于通道的发送和接收操作是阻塞的,在一些简单场景下可能会引入不必要的开销,比如频繁的通道操作。

代码复杂度差异

  1. 互斥锁
    • 优点:代码相对简单直观,只需在访问共享资源前后添加锁操作。
    • 缺点:需要小心处理锁的粒度和死锁问题。如果锁的粒度太大,会降低并发性能;如果锁的粒度太小,又可能导致死锁。
  2. 通道
    • 优点:代码逻辑相对清晰,通过通道传递数据避免了共享资源的直接操作,减少了死锁风险。
    • 缺点:通道的使用需要更多的设计和规划,比如通道的缓冲大小、如何正确地关闭通道等,增加了代码复杂度。