MST

星途 面试题库

面试题:Go中启动Goroutine的常规方式及注意事项

请描述在Go语言中启动Goroutine的常规方法,并且说明在启动Goroutine时可能会遇到哪些常见问题,例如资源竞争等,以及如何预防这些问题。
18.0万 热度难度
编程语言Go

知识考点

AI 面试

面试题答案

一键面试

启动Goroutine的常规方法

在Go语言中,使用 go 关键字来启动一个Goroutine。语法如下:

go functionName(parameters)

例如:

package main

import (
    "fmt"
)

func printHello() {
    fmt.Println("Hello from goroutine")
}

func main() {
    go printHello()
    fmt.Println("Main function")
}

在上述代码中,go printHello() 启动了一个新的Goroutine来执行 printHello 函数。主函数会继续执行而不会等待这个Goroutine完成。

常见问题及预防方法

  1. 资源竞争(Race Condition)
    • 问题描述:当多个Goroutine同时访问和修改共享资源时,可能会导致数据不一致或未定义行为。例如,多个Goroutine同时对一个共享变量进行读写操作。
    • 预防方法
      • 互斥锁(Mutex):使用 sync.Mutex 来保护共享资源。在访问共享资源前加锁,访问结束后解锁。
        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)
        }
        
      • 读写锁(RWMutex):当读操作远多于写操作时,可以使用 sync.RWMutex。允许多个Goroutine同时进行读操作,但写操作需要独占锁。
        package main
        
        import (
            "fmt"
            "sync"
        )
        
        var (
            data  int
            rwmu  sync.RWMutex
        )
        
        func read(wg *sync.WaitGroup) {
            defer wg.Done()
            rwmu.RLock()
            fmt.Println("Read value:", data)
            rwmu.RUnlock()
        }
        
        func write(wg *sync.WaitGroup) {
            defer wg.Done()
            rwmu.Lock()
            data++
            rwmu.Unlock()
        }
        
        func main() {
            var wg sync.WaitGroup
            for i := 0; i < 5; i++ {
                wg.Add(1)
                go read(&wg)
            }
            for i := 0; i < 2; i++ {
                wg.Add(1)
                go write(&wg)
            }
            wg.Wait()
        }
        
  2. 死锁(Deadlock)
    • 问题描述:当两个或多个Goroutine相互等待对方释放锁,从而导致程序无法继续执行。
    • 预防方法
      • 合理设计锁的获取顺序:确保所有Goroutine以相同的顺序获取锁,避免循环依赖。
      • 使用 context:设置操作的截止时间或取消操作,避免无限期等待。例如:
        package main
        
        import (
            "context"
            "fmt"
            "sync"
            "time"
        )
        
        func worker(ctx context.Context, wg *sync.WaitGroup) {
            defer wg.Done()
            select {
            case <-ctx.Done():
                fmt.Println("Worker stopped due to context cancellation")
            case <-time.After(2 * time.Second):
                fmt.Println("Worker completed task")
            }
        }
        
        func main() {
            var wg sync.WaitGroup
            ctx, cancel := context.WithTimeout(context.Background(), 1*time.Second)
            defer cancel()
        
            wg.Add(1)
            go worker(ctx, &wg)
            wg.Wait()
        }
        
  3. Goroutine泄漏(Goroutine Leak)
    • 问题描述:当Goroutine在后台持续运行,且无法结束,占用系统资源,导致内存泄漏等问题。
    • 预防方法
      • 正确使用通道(Channel):确保在Goroutine中正确关闭通道,避免Goroutine因等待通道而无法结束。例如:
        package main
        
        import (
            "fmt"
        )
        
        func producer(ch chan int) {
            for i := 0; i < 5; i++ {
                ch <- i
            }
            close(ch)
        }
        
        func consumer(ch chan int) {
            for val := range ch {
                fmt.Println("Consumed:", val)
            }
        }
        
        func main() {
            ch := make(chan int)
            go producer(ch)
            go consumer(ch)
            // 适当的等待逻辑
            select {}
        }
        
      • 使用 context:通过 context 来取消Goroutine,确保在程序结束或特定条件下,Goroutine能正确终止。