MST

星途 面试题库

面试题:Go中goroutine异常处理对程序并发安全的影响

在一个高并发的Go程序中,多个goroutine同时读写共享资源。假设其中一个goroutine在对共享资源操作时发生异常,通过`recover`捕获了该异常。请阐述这对整个程序的并发安全性会产生哪些影响,以及如何确保在异常处理后程序仍然保持并发安全,可结合`sync`包相关知识作答。
19.1万 热度难度
编程语言Go

知识考点

AI 面试

面试题答案

一键面试
  1. 异常对并发安全性的影响
    • 当一个goroutine通过recover捕获到异常时,该goroutine内部的局部状态可能会因为异常处理而改变。如果共享资源的状态依赖于这个goroutine的正常执行流程,那么异常处理可能会导致共享资源处于不一致状态。
    • 其他正在读写共享资源的goroutine可能会读到这个不一致的状态,从而破坏并发安全性。例如,假设共享资源是一个计数器,正常情况下应该递增,但在异常处理中可能会被重置或者没有正确递增,其他goroutine读取到的计数器值就可能不正确。
  2. 确保异常处理后程序保持并发安全的方法
    • 使用sync.Mutex
      • 在对共享资源进行读写操作前,先获取锁。在异常处理后,确保释放锁。例如:
package main

import (
    "fmt"
    "sync"
)

var (
    sharedResource int
    mu             sync.Mutex
)

func worker(wg *sync.WaitGroup) {
    defer wg.Done()
    mu.Lock()
    defer mu.Unlock()
    // 模拟可能发生异常的操作
    if sharedResource < 0 {
        panic("negative value not allowed")
    }
    sharedResource++
}

func main() {
    var wg sync.WaitGroup
    for i := 0; i < 10; i++ {
        wg.Add(1)
        go worker(&wg)
    }
    wg.Wait()
    fmt.Println("Final sharedResource:", sharedResource)
}
  • 使用sync.RWMutex
    • 如果读操作远多于写操作,可以使用读写锁。在写操作(可能发生异常的操作)时获取写锁,读操作时获取读锁。例如:
package main

import (
    "fmt"
    "sync"
)

var (
    sharedResource int
    rwmu           sync.RWMutex
)

func reader(wg *sync.WaitGroup) {
    defer wg.Done()
    rwmu.RLock()
    defer rwmu.RUnlock()
    fmt.Println("Reader reads:", sharedResource)
}

func writer(wg *sync.WaitGroup) {
    defer wg.Done()
    rwmu.Lock()
    defer rwmu.Unlock()
    // 模拟可能发生异常的操作
    if sharedResource < 0 {
        panic("negative value not allowed")
    }
    sharedResource++
}

func main() {
    var wg sync.WaitGroup
    for i := 0; i < 5; i++ {
        wg.Add(1)
        go reader(&wg)
    }
    for i := 0; i < 3; i++ {
        wg.Add(1)
        go writer(&wg)
    }
    wg.Wait()
    fmt.Println("Final sharedResource:", sharedResource)
}
  • 使用sync.Cond
    • 当需要基于共享资源的状态进行条件等待和唤醒时,可以结合sync.Mutexsync.Cond。在异常处理后,需要正确地通知其他等待的goroutine,确保共享资源状态变化后能被及时感知。例如:
package main

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

var (
    sharedResource int
    mu             sync.Mutex
    cond           = sync.NewCond(&mu)
)

func waiter(wg *sync.WaitGroup) {
    defer wg.Done()
    mu.Lock()
    for sharedResource < 10 {
        cond.Wait()
    }
    fmt.Println("Waiter sees:", sharedResource)
    mu.Unlock()
}

func changer(wg *sync.WaitGroup) {
    defer wg.Done()
    mu.Lock()
    // 模拟可能发生异常的操作
    if sharedResource < 0 {
        panic("negative value not allowed")
    }
    sharedResource++
    if sharedResource == 10 {
        cond.Broadcast()
    }
    mu.Unlock()
}

func main() {
    var wg sync.WaitGroup
    for i := 0; i < 3; i++ {
        wg.Add(1)
        go waiter(&wg)
    }
    for i := 0; i < 10; i++ {
        wg.Add(1)
        go changer(&wg)
    }
    time.Sleep(2 * time.Second)
    fmt.Println("Final sharedResource:", sharedResource)
}

通过这些sync包的工具,可以在异常处理后仍然保持程序的并发安全。在异常处理时,要确保锁的正确获取和释放,以及条件变量的正确通知,这样才能保证共享资源的状态在并发环境下是一致和安全的。