MST

星途 面试题库

面试题:Go并发编程里的资源管理与健壮性保障

在Go的并发程序中,常常涉及到对共享资源(如数据库连接、文件句柄等)的操作。请阐述如何确保在并发环境下对这些资源的正确管理和释放,以提高程序的健壮性。并讨论sync包中的哪些工具可以辅助完成这项工作,举例说明其用法。
22.7万 热度难度
编程语言Go

知识考点

AI 面试

面试题答案

一键面试

确保共享资源正确管理和释放的方法

  1. 使用互斥锁(Mutex)
    • 当多个goroutine需要访问共享资源时,通过互斥锁来保证同一时间只有一个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
    for i := 0; i < 10; i++ {
        wg.Add(1)
        go increment(&wg)
    }
    wg.Wait()
    fmt.Println("Final counter value:", counter)
}
  1. 读写锁(RWMutex)
    • 如果共享资源读操作远多于写操作,可以使用读写锁。多个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 key %s, value %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 key %s, value %d\n", key, value)
    rwmu.Unlock()
}

func main() {
    var wg sync.WaitGroup
    wg.Add(2)
    go write("test", 100, &wg)
    go read("test", &wg)
    wg.Wait()
}
  1. 使用sync.WaitGroup
    • 用于等待一组goroutine完成。在启动每个goroutine前调用wg.Add(1),在goroutine结束时调用wg.Done(),主goroutine通过wg.Wait()等待所有goroutine完成。
    • 如上面的例子中,通过WaitGroup确保所有incrementread/write操作完成后程序才结束。
  2. 使用sync.Cond
    • 当需要在共享资源的状态发生变化时通知其他goroutine时使用。例如,一个生产者 - 消费者模型中,当缓冲区满时生产者等待,当缓冲区有空间时消费者通知生产者。
    • 示例:
package main

import (
    "fmt"
    "sync"
)

type Buffer struct {
    data  []int
    count int
    size  int
    mu    sync.Mutex
    cond  sync.Cond
}

func NewBuffer(size int) *Buffer {
    b := &Buffer{
        data:  make([]int, 0, size),
        size:  size,
    }
    b.cond.L = &b.mu
    return b
}

func (b *Buffer) Produce(item int) {
    b.mu.Lock()
    for b.count == b.size {
        b.cond.Wait()
    }
    b.data = append(b.data, item)
    b.count++
    fmt.Printf("Produced %d, buffer count %d\n", item, b.count)
    b.cond.Broadcast()
    b.mu.Unlock()
}

func (b *Buffer) Consume() int {
    b.mu.Lock()
    for b.count == 0 {
        b.cond.Wait()
    }
    item := b.data[0]
    b.data = b.data[1:]
    b.count--
    fmt.Printf("Consumed %d, buffer count %d\n", item, b.count)
    b.cond.Broadcast()
    b.mu.Unlock()
    return item
}

func main() {
    buffer := NewBuffer(2)
    var wg sync.WaitGroup
    wg.Add(3)
    go func() {
        buffer.Produce(1)
        wg.Done()
    }()
    go func() {
        buffer.Produce(2)
        wg.Done()
    }()
    go func() {
        buffer.Consume()
        wg.Done()
    }()
    wg.Wait()
}
  1. 资源清理与defer
    • 在函数中使用defer关键字来确保资源在函数结束时正确释放。例如,对于文件句柄,在打开文件后使用defer file.Close()确保文件在函数结束时关闭。
package main

import (
    "fmt"
    "os"
)

func readFile() {
    file, err := os.Open("test.txt")
    if err != nil {
        fmt.Println("Error opening file:", err)
        return
    }
    defer file.Close()
    // 处理文件内容
}
  1. 使用sync.Once
    • 用于确保某个操作只执行一次。例如,对于数据库连接池的初始化,使用sync.Once可以保证在多个goroutine环境下连接池只初始化一次。
package main

import (
    "fmt"
    "sync"
)

var (
    dbOnce sync.Once
    dbConn string
)

func getDBConnection() string {
    dbOnce.Do(func() {
        // 实际的数据库连接操作
        dbConn = "connected to database"
        fmt.Println("Database connection initialized")
    })
    return dbConn
}

func main() {
    var wg sync.WaitGroup
    for i := 0; i < 3; i++ {
        wg.Add(1)
        go func() {
            defer wg.Done()
            conn := getDBConnection()
            fmt.Println(conn)
        }()
    }
    wg.Wait()
}

通过合理使用这些工具,可以在Go的并发程序中有效管理和释放共享资源,提高程序的健壮性。