Skip to content

并发编程

Go 语言的并发模型是其最重要的特性之一,通过 Goroutine 和 Channel 实现优雅的并发编程。

Goroutine

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

创建 Goroutine

使用 go 关键字创建 Goroutine:

go
func sayHello() {
    fmt.Println("Hello")
}

func main() {
    // 启动一个新的 Goroutine
    go sayHello()
    
    // 主 Goroutine 继续执行
    fmt.Println("World")
    
    // 等待一下,让 Goroutine 有时间执行
    time.Sleep(100 * time.Millisecond)
}

匿名函数 Goroutine

go
go func() {
    fmt.Println("匿名函数 Goroutine")
}()

// 带参数
go func(msg string) {
    fmt.Println(msg)
}("Hello from Goroutine")

多个 Goroutine

go
func printNumber(n int) {
    for i := 0; i < n; i++ {
        fmt.Printf("Goroutine %d: %d\n", n, i)
        time.Sleep(100 * time.Millisecond)
    }
}

func main() {
    go printNumber(1)
    go printNumber(2)
    go printNumber(3)
    
    time.Sleep(2 * time.Second)
}

Channel

Channel 是 Goroutine 之间通信的管道。

创建 Channel

go
// 无缓冲 Channel
ch := make(chan int)

// 有缓冲 Channel
ch := make(chan int, 10)

发送和接收

go
ch := make(chan string)

// 发送数据
go func() {
    ch <- "Hello"
}()

// 接收数据
msg := <-ch
fmt.Println(msg)

无缓冲 Channel

无缓冲 Channel 是同步的,发送和接收必须同时准备好:

go
ch := make(chan int)

go func() {
    ch <- 42  // 发送
}()

value := <-ch  // 接收
fmt.Println(value)

有缓冲 Channel

有缓冲 Channel 可以存储多个值:

go
ch := make(chan int, 3)

ch <- 1
ch <- 2
ch <- 3

fmt.Println(<-ch)  // 1
fmt.Println(<-ch)  // 2
fmt.Println(<-ch)  // 3

Channel 方向

可以指定 Channel 的方向:

go
// 只发送
func sendOnly(ch chan<- int) {
    ch <- 42
}

// 只接收
func receiveOnly(ch <-chan int) {
    value := <-ch
    fmt.Println(value)
}

关闭 Channel

go
ch := make(chan int)

go func() {
    for i := 0; i < 5; i++ {
        ch <- i
    }
    close(ch)  // 关闭 Channel
}()

// 接收直到 Channel 关闭
for value := range ch {
    fmt.Println(value)
}

// 检查 Channel 是否关闭
value, ok := <-ch
if !ok {
    fmt.Println("Channel 已关闭")
}

Select

select 语句用于在多个 Channel 操作中选择一个执行。

基本用法

go
ch1 := make(chan string)
ch2 := make(chan string)

go func() {
    time.Sleep(1 * time.Second)
    ch1 <- "来自 ch1"
}()

go func() {
    time.Sleep(2 * time.Second)
    ch2 <- "来自 ch2"
}()

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

默认分支

go
select {
case msg := <-ch:
    fmt.Println(msg)
default:
    fmt.Println("没有消息")
}

超时处理

go
select {
case msg := <-ch:
    fmt.Println(msg)
case <-time.After(1 * time.Second):
    fmt.Println("超时")
}

多个 Channel

go
for {
    select {
    case msg1 := <-ch1:
        fmt.Println("ch1:", msg1)
    case msg2 := <-ch2:
        fmt.Println("ch2:", msg2)
    case <-done:
        return
    }
}

同步原语

sync.Mutex

互斥锁用于保护共享资源:

go
import "sync"

var (
    counter int
    mutex   sync.Mutex
)

func increment() {
    mutex.Lock()
    defer mutex.Unlock()
    counter++
}

func main() {
    var wg sync.WaitGroup
    
    for i := 0; i < 1000; i++ {
        wg.Add(1)
        go func() {
            defer wg.Done()
            increment()
        }()
    }
    
    wg.Wait()
    fmt.Println("Counter:", counter)
}

sync.RWMutex

读写锁,允许多个读操作或一个写操作:

go
var (
    data  map[string]string
    rwMutex sync.RWMutex
)

func read(key string) string {
    rwMutex.RLock()
    defer rwMutex.RUnlock()
    return data[key]
}

func write(key, value string) {
    rwMutex.Lock()
    defer rwMutex.Unlock()
    data[key] = value
}

sync.WaitGroup

等待一组 Goroutine 完成:

go
var wg sync.WaitGroup

func worker(id int) {
    defer wg.Done()
    fmt.Printf("Worker %d 开始\n", id)
    time.Sleep(1 * time.Second)
    fmt.Printf("Worker %d 完成\n", id)
}

func main() {
    for i := 1; i <= 3; i++ {
        wg.Add(1)
        go worker(i)
    }
    
    wg.Wait()
    fmt.Println("所有 Worker 完成")
}

sync.Once

确保某个操作只执行一次:

go
var once sync.Once

func setup() {
    fmt.Println("初始化")
}

func main() {
    once.Do(setup)
    once.Do(setup)  // 不会执行
}

并发模式

Worker Pool

go
func worker(id int, jobs <-chan int, results chan<- int) {
    for job := range jobs {
        fmt.Printf("Worker %d 处理任务 %d\n", id, job)
        time.Sleep(1 * time.Second)
        results <- job * 2
    }
}

func main() {
    jobs := make(chan int, 100)
    results := make(chan int, 100)
    
    // 启动 3 个 Worker
    for w := 1; w <= 3; w++ {
        go worker(w, jobs, results)
    }
    
    // 发送任务
    for j := 1; j <= 5; j++ {
        jobs <- j
    }
    close(jobs)
    
    // 收集结果
    for r := 1; r <= 5; r++ {
        fmt.Println("结果:", <-results)
    }
}

管道模式

go
func numbers() <-chan int {
    out := make(chan int)
    go func() {
        for i := 0; i < 5; i++ {
            out <- i
        }
        close(out)
    }()
    return out
}

func square(in <-chan int) <-chan int {
    out := make(chan int)
    go func() {
        for n := range in {
            out <- n * n
        }
        close(out)
    }()
    return out
}

func main() {
    nums := numbers()
    squares := square(nums)
    
    for s := range squares {
        fmt.Println(s)
    }
}

完整示例

go
package main

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

func main() {
    // Channel 示例
    ch := make(chan string, 2)
    
    go func() {
        ch <- "消息 1"
        ch <- "消息 2"
        close(ch)
    }()
    
    for msg := range ch {
        fmt.Println(msg)
    }
    
    // WaitGroup 示例
    var wg sync.WaitGroup
    
    for i := 1; i <= 3; i++ {
        wg.Add(1)
        go func(id int) {
            defer wg.Done()
            fmt.Printf("Goroutine %d 执行\n", id)
            time.Sleep(100 * time.Millisecond)
        }(i)
    }
    
    wg.Wait()
    fmt.Println("所有 Goroutine 完成")
}

最佳实践

  1. 使用 Channel 进行通信,而不是共享内存
  2. 避免在 Goroutine 之间共享可变状态
  3. 使用 Context 管理 Goroutine 生命周期
  4. 合理使用缓冲 Channel 提高性能
  5. 注意 Goroutine 泄漏问题

下一步

接下来我们将学习:

基于 VitePress 构建