Goroutine 入门

Goroutine 是 Go 语言并发编程的核心特性。本章将介绍 Goroutine 的基本概念和使用方法。

什么是 Goroutine?

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

Goroutine vs 线程:

┌─────────────────────────────────────────────────┐
│              Goroutine                          │
├─────────────────────────────────────────────────┤
│  初始栈大小: 2KB(可动态增长)                   │
│  创建开销: 微秒级                                │
│  切换开销: 用户态切换,无需内核参与              │
│  调度: Go 运行时调度                            │
│  数量: 可以轻松创建百万个                        │
└─────────────────────────────────────────────────┘

┌─────────────────────────────────────────────────┐
│              操作系统线程                        │
├─────────────────────────────────────────────────┤
│  初始栈大小: 1MB                                 │
│  创建开销: 毫秒级                                │
│  切换开销: 需要内核参与                          │
│  调度: 操作系统调度                              │
│  数量: 通常几千个                                │
└─────────────────────────────────────────────────┘

创建 Goroutine

使用 go 关键字

package main

import (
    "fmt"
    "time"
)

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

func main() {
    go sayHello()

    fmt.Println("Hello from main!")

    time.Sleep(time.Second)
}

匿名函数

package main

import (
    "fmt"
    "time"
)

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

    go func(name string) {
        fmt.Printf("带参数: %s\n", name)
    }("张三")

    time.Sleep(time.Second)
}

Goroutine 调度

主 Goroutine

main 函数运行在主 goroutine 中,当主 goroutine 结束,所有其他 goroutine 也会结束:

package main

import (
    "fmt"
    "time"
)

func main() {
    go func() {
        time.Sleep(2 * time.Second)
        fmt.Println("这行可能不会输出")
    }()

    time.Sleep(time.Second)
    fmt.Println("main 结束")
}

让出 CPU

使用 runtime.Gosched() 让出 CPU:

package main

import (
    "fmt"
    "runtime"
)

func main() {
    go func() {
        for i := 0; i < 5; i++ {
            fmt.Printf("A: %d\n", i)
            runtime.Gosched()
        }
    }()

    for i := 0; i < 5; i++ {
        fmt.Printf("B: %d\n", i)
        runtime.Gosched()
    }
}

获取 Goroutine 数量

package main

import (
    "fmt"
    "runtime"
    "time"
)

func main() {
    fmt.Printf("初始 goroutine 数量: %d\n", runtime.NumGoroutine())

    for i := 0; i < 10; i++ {
        go func(n int) {
            time.Sleep(time.Second)
            fmt.Printf("goroutine %d 完成\n", n)
        }(i)
    }

    fmt.Printf("创建后 goroutine 数量: %d\n", runtime.NumGoroutine())

    time.Sleep(2 * time.Second)
    fmt.Printf("完成后 goroutine 数量: %d\n", runtime.NumGoroutine())
}

并发 vs 并行

package main

import (
    "fmt"
    "runtime"
    "time"
)

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

func main() {
    runtime.GOMAXPROCS(2)

    for i := 1; i <= 3; i++ {
        go worker(i)
    }

    time.Sleep(time.Second)
}

WaitGroup 等待 Goroutine

package main

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

func worker(id int, wg *sync.WaitGroup) {
    defer wg.Done()

    fmt.Printf("worker %d 开始\n", id)
    time.Sleep(time.Second)
    fmt.Printf("worker %d 完成\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("所有 worker 完成")
}

Goroutine 陷阱

闭包变量问题

package main

import (
    "fmt"
    "time"
)

func main() {
    for i := 0; i < 3; i++ {
        go func() {
            fmt.Printf("i = %d\n", i)
        }()
    }
    time.Sleep(time.Second)
}

输出可能是:

i = 3
i = 3
i = 3

解决方法:传递参数

package main

import (
    "fmt"
    "time"
)

func main() {
    for i := 0; i < 3; i++ {
        go func(n int) {
            fmt.Printf("n = %d\n", n)
        }(i)
    }
    time.Sleep(time.Second)
}

实际案例

并发下载

package main

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

func download(url string, wg *sync.WaitGroup) {
    defer wg.Done()

    fmt.Printf("开始下载: %s\n", url)
    time.Sleep(time.Second)
    fmt.Printf("下载完成: %s\n", url)
}

func main() {
    urls := []string{
        "https://example.com/file1",
        "https://example.com/file2",
        "https://example.com/file3",
    }

    var wg sync.WaitGroup
    start := time.Now()

    for _, url := range urls {
        wg.Add(1)
        go download(url, &wg)
    }

    wg.Wait()
    fmt.Printf("总耗时: %v\n", time.Since(start))
}

总结

本章学习了 Go 语言的 Goroutine:

知识点说明
创建go func()
轻量初始 2KB 栈,可动态增长
调度Go 运行时调度
WaitGroup等待多个 goroutine 完成
闭包陷阱循环变量需要传参