MST

星途 面试题库

面试题:Go语言并发场景下的深度防御性编程策略

在高并发的Go程序中,存在多个goroutine共享数据的场景。从防御性编程视角出发,如何设计一个健壮的机制来确保数据一致性和程序稳定性,防止竞态条件、死锁等问题?请结合sync包等相关知识给出完整且优化的设计方案与代码示例。
10.8万 热度难度
编程语言Go

知识考点

AI 面试

面试题答案

一键面试
  1. 使用sync.Mutex保护共享数据
    • sync.Mutex是Go语言中用于同步访问共享资源的基础工具。通过锁定和解锁互斥锁,可以确保在同一时间只有一个goroutine能够访问共享数据,从而避免竞态条件。
    • 示例代码:
package main

import (
    "fmt"
    "sync"
)

var (
    counter int
    mu      sync.Mutex
)

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

func main() {
    var wg sync.WaitGroup
    numGoroutines := 1000
    for i := 0; i < numGoroutines; i++ {
        wg.Add(1)
        go increment(&wg)
    }
    wg.Wait()
    fmt.Println("Final counter value:", counter)
}
  1. 使用sync.RWMutex进行读写分离(适用于读多写少场景)
    • sync.RWMutex允许多个goroutine同时进行读操作,但只允许一个goroutine进行写操作。这样在读多写少的场景下,可以提高程序的并发性能。
    • 示例代码:
package main

import (
    "fmt"
    "sync"
)

var (
    data  = make(map[string]int)
    rwmu  sync.RWMutex
)

func read(key string, wg *sync.WaitGroup) {
    defer wg.Done()
    rwmu.RLock()
    value := data[key]
    fmt.Printf("Read value for key %s: %d\n", key, value)
    rwmu.RUnlock()
}

func write(key string, value int, wg *sync.WaitGroup) {
    defer wg.Done()
    rwmu.Lock()
    data[key] = value
    fmt.Printf("Write value %d for key %s\n", value, key)
    rwmu.Unlock()
}

func main() {
    var wg sync.WaitGroup
    wg.Add(2)
    go write("test", 42, &wg)
    go read("test", &wg)
    wg.Wait()
}
  1. 避免死锁
    • 死锁原因:死锁通常发生在两个或多个goroutine相互等待对方释放锁的情况下。
    • 避免方法
      • 按照相同的顺序获取锁。例如,如果有两个锁mu1mu2,在所有的goroutine中都先获取mu1再获取mu2,这样可以避免死锁。
      • 使用context.Context来设置操作的超时。如果一个goroutine在获取锁时等待时间过长,可以通过context.Context的超时机制来取消操作,从而避免死锁。
    • 示例代码(使用context.Context避免死锁):
package main

import (
    "context"
    "fmt"
    "sync"
    "time"
)

var mu sync.Mutex

func worker(ctx context.Context) {
    select {
    case <-ctx.Done():
        fmt.Println("Operation cancelled due to timeout")
        return
    default:
    }
    // 尝试获取锁,设置超时
    ctx, cancel := context.WithTimeout(ctx, 100*time.Millisecond)
    defer cancel()
    select {
    case <-ctx.Done():
        fmt.Println("Timeout waiting for lock")
        return
    default:
        mu.Lock()
        defer mu.Unlock()
        fmt.Println("Lock acquired, doing work...")
        time.Sleep(200 * time.Millisecond)
    }
}

func main() {
    var wg sync.WaitGroup
    ctx, cancel := context.WithCancel(context.Background())
    wg.Add(2)
    go func() {
        defer wg.Done()
        worker(ctx)
    }()
    go func() {
        defer wg.Done()
        mu.Lock()
        time.Sleep(300 * time.Millisecond)
        mu.Unlock()
    }()
    time.Sleep(200 * time.Millisecond)
    cancel()
    wg.Wait()
}
  1. 使用sync.Cond进行条件等待
    • sync.Cond用于在某些条件满足时通知goroutine。例如,当共享数据达到某个状态时,唤醒等待的goroutine。
    • 示例代码:
package main

import (
    "fmt"
    "sync"
    "time"
)

var (
    cond  = sync.NewCond(&sync.Mutex{})
    ready = false
)

func waiter() {
    cond.L.Lock()
    for!ready {
        cond.Wait()
    }
    fmt.Println("Waiter got signal, doing work...")
    cond.L.Unlock()
}

func signaler() {
    time.Sleep(2 * time.Second)
    cond.L.Lock()
    ready = true
    fmt.Println("Signaler setting ready to true and broadcasting")
    cond.Broadcast()
    cond.L.Unlock()
}

func main() {
    var wg sync.WaitGroup
    wg.Add(2)
    go func() {
        defer wg.Done()
        waiter()
    }()
    go func() {
        defer wg.Done()
        signaler()
    }()
    wg.Wait()
}