面试题答案
一键面试启动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完成。
常见问题及预防方法
- 资源竞争(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() }
- 互斥锁(Mutex):使用
- 死锁(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() }
- 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能正确终止。
- 正确使用通道(Channel):确保在Goroutine中正确关闭通道,避免Goroutine因等待通道而无法结束。例如: