程序员面试宝典

一站式面试准备平台

返回分类
Go高级

Go 并发编程

深入理解 Go 语言的并发模型,包括 goroutine、channel、select 等核心概念

2026-03-16
阅读时间: 8分钟

Go 并发编程

Goroutine

Goroutine 是 Go 语言中的轻量级线程,由 Go 运行时管理。

go
package main

import (
    "fmt"
    "time"
)

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

func main() {
    // 启动一个 goroutine
    go sayHello()
    
    // 主 goroutine 继续执行
    fmt.Println("Hello from main!")
    
    // 等待一下,让 goroutine 有机会执行
    time.Sleep(time.Millisecond * 100)
}

Channel

Channel 是 goroutine 之间通信的管道。

基本用法

go
package main

import "fmt"

func main() {
    // 创建一个无缓冲 channel
    ch := make(chan string)
    
    go func() {
        ch <- "Hello from goroutine!"
    }()
    
    // 从 channel 接收数据
    msg := <-ch
    fmt.Println(msg) // Hello from goroutine!
}

缓冲 Channel

go
package main

import "fmt"

func main() {
    // 创建一个缓冲大小为 2 的 channel
    ch := make(chan int, 2)
    
    ch <- 1
    ch <- 2
    
    fmt.Println(<-ch) // 1
    fmt.Println(<-ch) // 2
}

Select 语句

select 语句用于处理多个 channel 的发送和接收操作。

go
package main

import (
    "fmt"
    "time"
)

func main() {
    ch1 := make(chan string)
    ch2 := make(chan string)
    
    go func() {
        time.Sleep(time.Second * 1)
        ch1 <- "from ch1"
    }()
    
    go func() {
        time.Sleep(time.Second * 2)
        ch2 <- "from ch2"
    }()
    
    select {
    case msg1 := <-ch1:
        fmt.Println("Received:", msg1)
    case msg2 := <-ch2:
        fmt.Println("Received:", msg2)
    case <-time.After(time.Second * 3):
        fmt.Println("Timeout!")
    }
}

WaitGroup

WaitGroup 用于等待一组 goroutine 完成。

go
package main

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

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

func main() {
    var wg sync.WaitGroup
    
    for i := 1; i <= 5; i++ {
        wg.Add(1)
        go worker(i, &wg)
    }
    
    wg.Wait()
    fmt.Println("All workers completed")
}

常见并发模式

Worker Pool

go
package main

import (
    "fmt"
    "sync"
)

func worker(id int, jobs <-chan int, results chan<- int, wg *sync.WaitGroup) {
    defer wg.Done()
    
    for job := range jobs {
        fmt.Printf("Worker %d processing job %d\n", id, job)
        results <- job * 2
    }
}

func main() {
    const numJobs = 10
    const numWorkers = 3
    
    jobs := make(chan int, numJobs)
    results := make(chan int, numJobs)
    var wg sync.WaitGroup
    
    // 启动 workers
    for w := 1; w <= numWorkers; w++ {
        wg.Add(1)
        go worker(w, jobs, results, &wg)
    }
    
    // 发送 jobs
    for j := 1; j <= numJobs; j++ {
        jobs <- j
    }
    close(jobs)
    
    // 等待所有 workers 完成
    go func() {
        wg.Wait()
        close(results)
    }()
    
    // 收集结果
    for result := range results {
        fmt.Println("Result:", result)
    }
}

常见面试题

1. 实现并发安全的计数器

go
type SafeCounter struct {
    mu    sync.Mutex
    count int
}

func (c *SafeCounter) Increment() {
    c.mu.Lock()
    defer c.mu.Unlock()
    c.count++
}

func (c *SafeCounter) Value() int {
    c.mu.Lock()
    defer c.mu.Unlock()
    return c.count
}

2. 实现带超时的并发请求

go
func fetchWithTimeout(url string, timeout time.Duration) (string, error) {
    result := make(chan string, 1)
    errChan := make(chan error, 1)
    
    go func() {
        // 模拟网络请求
        time.Sleep(time.Second * 2)
        result <- "response data"
    }()
    
    select {
    case res := <-result:
        return res, nil
    case err := <-errChan:
        return "", err
    case <-time.After(timeout):
        return "", fmt.Errorf("request timeout")
    }
}

最佳实践

  1. 使用 defer 解锁:确保锁总是被释放
  2. 避免共享内存:优先使用 channel 通信
  3. 控制 goroutine 数量:避免创建过多 goroutine
  4. 处理 panic:在 goroutine 中使用 recover
  5. 使用 context 控制生命周期:特别是对于网络请求

相关标签