Go 并发编程入门,goroutine 和 channel 就这么简单

Go 最大的卖点就是并发。一个 go 关键字就能启动一个协程,几万个协程也就几十 MB 内存。比 Java 的线程轻量太多了。

goroutine

go 复制代码
package main

import (
    "fmt"
    "time"
)

func sayHello(name string) {
    for i := 0; i < 3; i++ {
        fmt.Printf("Hello from %s\n", name)
        time.Sleep(100 * time.Millisecond)
    }
}

func main() {
    go sayHello("goroutine") // 启动一个 goroutine
    sayHello("main")         // main 函数自己也在跑
}

go 关键字 + 函数调用 = 启动一个 goroutine。就这么简单。

输出会是交替的(两个 goroutine 并发执行)。

goroutine 很轻

go 复制代码
func main() {
    for i := 0; i < 100000; i++ {
        go func(n int) {
            // 10 万个 goroutine
        }(i)
    }
    time.Sleep(time.Second)
}

10 万个 goroutine 大约占 100MB 内存(每个 goroutine 初始栈只有 2KB)。Java 里 10 万个线程?直接 OOM。

channel --- goroutine 之间的通信

Go 的并发哲学是"不要通过共享内存来通信,而是通过通信来共享内存"。

go 复制代码
// 创建 channel
ch := make(chan int)      // 无缓冲
ch := make(chan int, 10)  // 有缓冲(容量 10)

// 发送
ch <- 42

// 接收
value := <-ch

基本用法

go 复制代码
func producer(ch chan int) {
    for i := 0; i < 5; i++ {
        ch <- i
        fmt.Println("produced:", i)
    }
    close(ch) // 生产完了,关闭 channel
}

func consumer(ch chan int) {
    for v := range ch { // 从 channel 读取,直到关闭
        fmt.Println("consumed:", v)
        time.Sleep(200 * time.Millisecond)
    }
}

func main() {
    ch := make(chan int, 5)
    go producer(ch)
    go consumer(ch)
    time.Sleep(2 * time.Second)
}

select 多路复用

go 复制代码
func main() {
    ch1 := make(chan string)
    ch2 := make(chan string)

    go func() {
        time.Sleep(100 * time.Millisecond)
        ch1 <- "from ch1"
    }()

    go func() {
        time.Sleep(200 * time.Millisecond)
        ch2 <- "from ch2"
    }()

    // 等待任意一个 channel 有数据
    select {
    case msg := <-ch1:
        fmt.Println(msg)
    case msg := <-ch2:
        fmt.Println(msg)
    case <-time.After(500 * time.Millisecond):
        fmt.Println("timeout")
    }
}

select 类似 switch,但是监听的是 channel 操作。哪个 channel 先有数据就走哪个 case。

WaitGroup --- 等待一组 goroutine 完成

go 复制代码
func main() {
    var wg sync.WaitGroup

    for i := 0; i < 5; i++ {
        wg.Add(1)
        go func(n int) {
            defer wg.Done()
            fmt.Println("goroutine", n, "done")
            time.Sleep(time.Duration(n) * 100 * time.Millisecond)
        }(i)
    }

    wg.Wait() // 阻塞,直到所有 wg.Done() 被调用
    fmt.Println("all done!")
}

Mutex --- 保护共享资源

虽然 Go 推荐用 channel 通信,但有时候还是需要对共享变量加锁:

go 复制代码
type Counter struct {
    mu    sync.Mutex
    count int
}

func (c *Counter) Increment() {
    c.mu.Lock()
    defer c.mu.Unlock() // 函数退出时自动解锁
    c.count++
}

func (c *Counter) Get() int {
    c.mu.Lock()
    defer c.mu.Unlock()
    return c.count
}

func main() {
    counter := &Counter{}
    var wg sync.WaitGroup

    for i := 0; i < 1000; i++ {
        wg.Add(1)
        go func() {
            defer wg.Done()
            counter.Increment()
        }()
    }

    wg.Wait()
    fmt.Println("count:", counter.Get()) // 1000
}

常见模式

生产者-消费者

go 复制代码
func main() {
    jobs := make(chan int, 100)
    results := make(chan int, 100)

    // 3 个 worker
    for w := 0; w < 3; w++ {
        go func(id int) {
            for j := range jobs {
                fmt.Printf("worker %d processing job %d\n", id, j)
                time.Sleep(200 * time.Millisecond)
                results <- j * 2
            }
        }(w)
    }

    // 发送 5 个任务
    for j := 0; j < 5; j++ {
        jobs <- j
    }
    close(jobs)

    // 收集结果
    for r := 0; r < 5; r++ {
        fmt.Println("result:", <-results)
    }
}

超时控制

go 复制代码
func fetchWithTimeout(url string, timeout time.Duration) (string, error) {
    ch := make(chan string, 1)
    go func() {
        // 模拟 HTTP 请求
        time.Sleep(2 * time.Second)
        ch <- "response data"
    }()

    select {
    case result := <-ch:
        return result, nil
    case <-time.After(timeout):
        return "", fmt.Errorf("request timeout after %v", timeout)
    }
}

总结

Go 并发三板斧:goroutine(轻量协程)、channel(协程间通信)、select(多路复用)。加锁用 sync.Mutex,等待用 sync.WaitGroup。goroutine 很便宜,随便起;channel 是 Go 并发的灵魂,优先用 channel 通信而不是共享内存加锁。

相关推荐
WAKU2 小时前
C#: 爸爸再爱我一回! 写在微软使用Rust重写GitHub Copilot之后的碎碎念
microsoft·rust·微软·c#·编程语言
柯南46686 天前
【AI开发之Rust】第 14 课:网络请求与 JSON —— reqwest + serde
rust·编程语言
Jay_276 天前
C语言和Java区别
编程语言
柯南46687 天前
【AI开发之Rust】第 13 课:async/await 与 tokio 异步运行时
rust·编程语言
柯南46688 天前
【AI开发之Rust】第 12 课:并发模型与同步原语
rust·编程语言
名字还没想好☜9 天前
Python sqlite3 实战:事务提交、参数化防注入、WAL 并发与 row_factory 取字典
后端·python·编程语言
柯南466810 天前
【AI开发之Rust】第 10 课:Cargo 工程化 —— 模块、依赖与测试(基础篇收尾)
rust·编程语言
QUOR11 天前
ZorvAI APK 插件框架 · 技术架构与功能介绍
github·apk·编程语言