引入

普通程序一次只做一件事,从头到尾按顺序执行。但很多时候需要同时处理多个任务——比如服务器同时响应多个请求。Go 从语言层面原生支持并发,用 goroutine 就能轻松启动一个并发执行单元,不需要手动管理线程。

正文

定义

定义

并发(concurrency)是程序同时处理多件事的能力,强调任务的结构和调度。并行(parallelism)是多件事真正同时执行,依赖多核 CPU。goroutine 是 Go 的轻量级并发执行单元,用 go 关键字启动,由 Go 运行时调度,比操作系统线程开销小得多。

语法

// 启动一个 goroutine
go func() {
    fmt.Println("在另一个 goroutine 中运行")
}()
 
// 启动一个普通函数作为 goroutine
go myFunc(arg1, arg2)

例子

启动 goroutine

func sayHello() {
    fmt.Println("hello")
}
 
func main() {
    go sayHello()
    time.Sleep(100 * time.Millisecond)
}

在函数调用前加 go 就启动了一个 goroutine,主函数不会等它执行完。这里用 time.Sleep() 让主函数等一下,否则程序直接退出,goroutine 来不及执行。

匿名函数 goroutine

func main() {
    go func() {
        for i := 0; i < 3; i++ {
            fmt.Println("goroutine:", i)
        }
    }()
 
    for i := 0; i < 3; i++ {
        fmt.Println("main:", i)
    }
    time.Sleep(100 * time.Millisecond)
}

匿名函数也能用 go 直接启动,适合短小的并发任务。

多个 goroutine

func worker(id int) {
    fmt.Printf("worker %d 开始\n", id)
    time.Sleep(100 * time.Millisecond)
    fmt.Printf("worker %d 完成\n", id)
}
 
func main() {
    for i := 1; i <= 3; i++ {
        go worker(i)
    }
    time.Sleep(200 * time.Millisecond)
}

多个 goroutine 并发执行,完成顺序不确定,每次运行结果可能不同。

goroutine 传参

func greet(name string) {
    fmt.Println("hello", name)
}
 
func main() {
    go greet("Alice")
    go greet("Bob")
    time.Sleep(100 * time.Millisecond)
}

启动 goroutine 时传入参数,每个 goroutine 持有自己独立的参数副本。

goroutine 闭包陷阱

func main() {
    for i := 0; i < 3; i++ {
        go func() {
            fmt.Println(i) // 可能全部输出 3
        }()
    }
    time.Sleep(100 * time.Millisecond)
}

闭包直接引用循环变量 igoroutine 执行时 i 可能已经变成了最终值。解决办法是传参

for i := 0; i < 3; i++ {
    go func(n int) {
        fmt.Println(n)
    }(i)
}

常见写法

写法说明
go func() { … }()匿名函数启动 goroutine
go myFunc(args)普通函数启动 goroutine
time.Sleep(d)临时等待(仅调试用)
runtime.NumGoroutine()获取当前 goroutine 数量
runtime.GOMAXPROCS(n)设置最大并行数

特点

  • goroutine 是 Go 的轻量级并发执行单元,用 go 关键字启动
  • 初始栈只有 2KB,可以轻松创建数万个
  • Go 运行时调度,不是操作系统线程
  • goroutinemain 函数)退出后,其他 goroutine 直接终止
  • goroutine 之间没有父子关系,无法从一个 goroutine 控制另一个
  • 闭包中引用循环变量要注意传参避免陷阱
  • time.Sleep() 只是临时手段,正式代码应该用 sync.WaitGroupchannel 来协调

理解

goroutine 就是 Go 给并发开的一条快车道。加个 go 关键字就能跑起来,不用操心线程池、回调这些东西。但要注意,goroutine 启动后主函数不等它,怎么让它们协调好、怎么安全地共享数据,就需要 channel 和各种同步工具了。

引出

goroutine 启动了,但多个 goroutine 之间怎么传递数据?接下来看 Go channel,学习 Go 里 goroutine 之间通信的管道。