在 Go 语言中,通常不使用操作系统线程,而是使用 goroutine。
goroutine 是 Go 运行时管理的轻量级“协程”,初始栈仅几 KB,创建销毁成本极低,可同时运行成千上万个。


  1. 启动 goroutine

在函数或方法调用前加上 go 关键字即可:

package main

import (
    "fmt"
    "time"
)

func say(s string) {
    for i := 0; i < 5; i++ {
        time.Sleep(100 * time.Millisecond)
        fmt.Println(s)
    }
}

func main() {
    go say("world") // 启动 goroutine
    say("hello")
}

注意:main 函数返回时,所有 goroutine 都会立即终止,不会等待它们完成。


  1. 等待 goroutine 完成 —— sync.WaitGroup

使用 sync.WaitGroup 等待一组 goroutine 结束:

package main

import (
    "fmt"
    "sync"
)

func worker(id int, wg *sync.WaitGroup) {
    defer wg.Done() // 计数器减1
    fmt.Printf("Worker %d starting\n", id)
    // 模拟工作
    fmt.Printf("Worker %d done\n", id)
}

func main() {
    var wg sync.WaitGroup

    for i := 1; i <= 5; i++ {
        wg.Add(1) // 计数器加1
        go worker(i, &wg)
    }

    wg.Wait() // 阻塞直到计数器归零
}

必须:在启动 goroutine 之前调用 wg.Add,并且将 wg 指针传给 goroutine。


  1. goroutine 间通信 —— channel

Go 的哲学:不要通过共享内存来通信,而应通过通信来共享内存。
channel 是 goroutine 间传递数据的管道。

无缓冲 channel(同步)

发送和接收操作会阻塞,直到另一端准备好:

ch := make(chan int)

go func() {
    ch <- 42 // 发送,阻塞直到有人接收
}()

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

有缓冲 channel(异步)

当缓冲未满时发送不阻塞,未空时接收不阻塞:

ch := make(chan string, 2)
ch <- "buffered"
ch <- "channel"
fmt.Println(<-ch)
fmt.Println(<-ch)

使用 range 和 close

发送方可以 close(ch) 关闭 channel,接收方用 for v := range ch 读取,直到 channel 关闭:

func fibonacci(n int, ch chan int) {
    x, y := 0, 1
    for i := 0; i < n; i++ {
        ch <- x
        x, y = y, x+y
    }
    close(ch)
}

func main() {
    ch := make(chan int, 10)
    go fibonacci(cap(ch), ch)
    for i := range ch {
        fmt.Println(i)
    }
}

  1. select 多路复用

select 可以同时等待多个 channel 操作:

select {
case v1 := <-ch1:
    fmt.Println("received", v1)
case v2 := <-ch2:
    fmt.Println("received", v2)
case ch3 <- 23:
    fmt.Println("sent on ch3")
default:
    fmt.Println("no communication")
}

常用于超时控制、非阻塞收发。


  1. 注意事项

· 不要泄漏 goroutine:确保 goroutine 最终能退出(比如通过关闭 channel 或使用 context 取消)。
· panic 处理:每个 goroutine 内部应该 recover,防止整个程序崩溃。
· GOMAXPROCS:默认使用所有 CPU 核,可调用 runtime.GOMAXPROCS(n) 调整,但一般不需要。
· 互斥锁:若必须共享内存,用 sync.Mutex 保护临界区。


  1. 简单示例:并发抓取 URL
package main

import (
    "fmt"
    "net/http"
    "sync"
    "time"
)

func fetchURL(url string, wg *sync.WaitGroup) {
    defer wg.Done()
    start := time.Now()
    resp, err := http.Get(url)
    if err != nil {
        fmt.Printf("%s -> error: %v\n", url, err)
        return
    }
    defer resp.Body.Close()
    fmt.Printf("%s -> %d, took %v\n", url, resp.StatusCode, time.Since(start))
}

func main() {
    urls := []string{
        "https://golang.org",
        "https://google.com",
        "https://github.com",
    }

    var wg sync.WaitGroup
    for _, url := range urls {
        wg.Add(1)
        go fetchURL(url, &wg)
    }
    wg.Wait()
}

掌握 goroutine + channel + WaitGroup,足以应对绝大多数并发场景。

Logo

Agent 垂直技术社区,欢迎活跃、内容共建。

更多推荐