Skip to content

interview

以下是一些关于 Go 中 Goroutine 的面试题,涵盖了基础概念、实际应用以及一些陷阱,适合不同层次的面试。


1. 基础概念

问题 1: 什么是 Goroutine?与线程的区别是什么?

答案

  • Goroutine 是 Go 中实现并发的轻量级线程,由 Go 运行时调度管理。
  • 与线程的区别
    1. 轻量级:Goroutine 初始栈大小约为 2KB,而线程栈大小通常为 1MB。
    2. 调度管理:Goroutine 使用 Go 运行时的 M:N 模型调度,多个 Goroutine 可绑定到多个操作系统线程。
    3. 创建开销低:启动一个 Goroutine 的开销比线程小得多。

问题 2: 如何启动一个 Goroutine?有什么注意事项?

答案

  • 使用 go 关键字启动 Goroutine。
  • 示例:
    go
    go func() {
        fmt.Println("Hello, Goroutine!")
    }()
  • 注意事项
    1. 启动 Goroutine 时,无法直接捕获其返回值。
    2. 主 Goroutine 在退出时会终止所有子 Goroutines,因此需要同步(如 sync.WaitGroup)。

2. 实际操作

问题 3: 写出一个程序,使用 Goroutine 打印数字 1 到 10,并同时打印字母 A 到 J。

答案

go
package main

import (
	"fmt"
	"time"
)

func printNumbers() {
	for i := 1; i <= 10; i++ {
		fmt.Printf("%d ", i)
		time.Sleep(100 * time.Millisecond)
	}
}

func printLetters() {
	for ch := 'A'; ch <= 'J'; ch++ {
		fmt.Printf("%c ", ch)
		time.Sleep(100 * time.Millisecond)
	}
}

func main() {
	go printNumbers()
	go printLetters()

	// 等待 Goroutines 执行完成
	time.Sleep(2 * time.Second)
}

问题 4: 什么是数据竞争?写一个有数据竞争的例子并修复它。

答案

  • 数据竞争:当多个 Goroutines 并发访问共享变量且至少一个 Goroutine 对变量进行修改,且没有同步措施时,可能发生数据竞争。

数据竞争的代码

go
package main

import "fmt"

var counter int

func increment() {
	for i := 0; i < 1000; i++ {
		counter++
	}
}

func main() {
	go increment()
	go increment()
	fmt.Scanln() // 等待 Goroutines 执行
	fmt.Println("Counter:", counter)
}

修复后的代码

  1. 使用 sync.Mutex
go
package main

import (
	"fmt"
	"sync"
)

var counter int
var mu sync.Mutex

func increment() {
	for i := 0; i < 1000; i++ {
		mu.Lock()
		counter++
		mu.Unlock()
	}
}

func main() {
	var wg sync.WaitGroup
	wg.Add(2)

	go func() {
		defer wg.Done()
		increment()
	}()

	go func() {
		defer wg.Done()
		increment()
	}()

	wg.Wait()
	fmt.Println("Counter:", counter)
}
  1. 使用 sync/atomic
go
package main

import (
	"fmt"
	"sync/atomic"
)

var counter int32

func increment() {
	for i := 0; i < 1000; i++ {
		atomic.AddInt32(&counter, 1)
	}
}

func main() {
	go increment()
	go increment()

	// 等待 Goroutines 执行完成
	fmt.Scanln()
	fmt.Println("Counter:", counter)
}

问题 5: 如何实现多个 Goroutine 的同步?

答案: 可以使用 sync.WaitGroup,例如:

go
package main

import (
	"fmt"
	"sync"
)

func worker(id int, wg *sync.WaitGroup) {
	defer wg.Done()
	fmt.Printf("Worker %d starting\n", id)
	fmt.Printf("Worker %d done\n", id)
}

func main() {
	var wg sync.WaitGroup

	for i := 1; i <= 3; i++ {
		wg.Add(1)
		go worker(i, &wg)
	}

	wg.Wait()
	fmt.Println("All workers finished")
}

3. 理论与陷阱

问题 6: 为什么 Goroutines 不需要显式销毁?

答案

  • Goroutines 是由 Go 运行时管理的,运行时会自动回收执行完成的 Goroutine 的资源。开发者无需显式销毁它们。

问题 7: 主 Goroutine 退出时,子 Goroutine 会怎么样?

答案

  • 主 Goroutine 退出时,程序终止,所有未完成的子 Goroutines 会被强制终止。因此,需要使用同步机制(如 sync.WaitGroup)来等待子 Goroutines 执行完成。

问题 8: Go 的 Goroutine 调度模型是什么?

答案

  • Go 使用的是 GMP 模型

    • G(Goroutine):表示 Goroutines。
    • M(Machine):表示操作系统线程。
    • P(Processor):逻辑处理器,负责调度 Goroutines 到线程。
  • 工作机制

    • 每个 P 维护一个本地运行队列,保存需要执行的 Goroutines。
    • 如果本地队列为空,P 会尝试从其他 P 的队列中偷取 Goroutines。

问题 9: 使用 Channel 解决生产者-消费者问题。

答案

go
package main

import (
	"fmt"
	"time"
)

func producer(ch chan int) {
	for i := 0; i < 5; i++ {
		fmt.Printf("Producing %d\n", i)
		ch <- i
		time.Sleep(1 * time.Second)
	}
	close(ch) // 关闭通道
}

func consumer(ch chan int) {
	for item := range ch {
		fmt.Printf("Consuming %d\n", item)
	}
}

func main() {
	ch := make(chan int, 3)
	go producer(ch)
	consumer(ch)
}

4. 高级问题

问题 10: 如何实现一个 Goroutine 泄漏的检测?

答案

  • Goroutine 泄漏是指 Goroutines 因未正确退出而长时间运行,浪费系统资源。
  • 可以使用 pprof 工具检测 Goroutine 泄漏。

示例

bash
go run -race main.go
go tool pprof http://localhost:6060/debug/pprof/goroutine

问题 11: Select 在 Goroutines 中的作用是什么?

答案select 用于处理多个 Channel 的操作:

  • 等待任意一个 Channel 就绪。
  • 非阻塞操作。

示例

go
package main

import "fmt"

func main() {
	ch1 := make(chan int)
	ch2 := make(chan int)

	go func() {
		ch1 <- 1
	}()
	go func() {
		ch2 <- 2
	}()

	select {
	case msg1 := <-ch1:
		fmt.Println("Received from ch1:", msg1)
	case msg2 := <-ch2:
		fmt.Println("Received from ch2:", msg2)
	}
}

这些问题涵盖了 Goroutines 的基础、实际使用场景以及一些常见陷阱和高级概念,能够帮助你准备面试并深入理解 Go 的并发编程模型。