Skip to content

goroutines.go

文件信息

  • 📄 原文件:01_goroutines.go
  • 🔤 语言:go

完整代码

go
package main

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

// ============================================================
//                      Goroutine 基础
// ============================================================
// Goroutine 是 Go 的轻量级线程,由 Go 运行时管理
// 比操作系统线程更轻量:初始栈只有 2KB,可动态增长
// 一个程序可以轻松创建数十万个 goroutine

func main() {
	fmt.Println("=== Goroutine 基础 ===")

	// ----------------------------------------------------------
	// 启动 Goroutine
	// ----------------------------------------------------------
	// 语法: go 函数调用
	// 【注意】main 函数退出时,所有 goroutine 都会被终止

	fmt.Println("主 goroutine 开始")

	// 启动一个新 goroutine
	go sayHello("世界")

	// 启动匿名函数 goroutine
	go func() {
		fmt.Println("匿名 goroutine")
	}()

	// 带参数的匿名 goroutine
	message := "你好"
	go func(msg string) {
		fmt.Println("带参数:", msg)
	}(message) // 【重要】参数在此传入,避免闭包陷阱

	// 等待一下让 goroutine 执行(生产代码不应该这样做)
	time.Sleep(100 * time.Millisecond)

	fmt.Println("主 goroutine 结束")

	// ----------------------------------------------------------
	// 多个 Goroutine
	// ----------------------------------------------------------
	fmt.Println("\n=== 多个 Goroutine ===")

	for i := 1; i <= 5; i++ {
		go func(n int) {
			fmt.Printf("Goroutine %d 开始\n", n)
			time.Sleep(time.Duration(n*10) * time.Millisecond)
			fmt.Printf("Goroutine %d 结束\n", n)
		}(i)
	}

	time.Sleep(200 * time.Millisecond)

	// ----------------------------------------------------------
	// WaitGroup:等待多个 goroutine 完成
	// ----------------------------------------------------------
	fmt.Println("\n=== WaitGroup ===")

	var wg sync.WaitGroup

	for i := 1; i <= 3; i++ {
		wg.Add(1) // 增加计数器

		go func(n int) {
			defer wg.Done() // 完成时减少计数器

			fmt.Printf("Worker %d 开始工作\n", n)
			time.Sleep(time.Duration(n*50) * time.Millisecond)
			fmt.Printf("Worker %d 完成工作\n", n)
		}(i)
	}

	wg.Wait() // 阻塞直到计数器为 0
	fmt.Println("所有 Worker 完成")

	// ----------------------------------------------------------
	// Goroutine 数量
	// ----------------------------------------------------------
	fmt.Println("\n=== Goroutine 信息 ===")

	fmt.Println("当前 Goroutine 数量:", runtime.NumGoroutine())
	fmt.Println("CPU 核心数:", runtime.NumCPU())
	fmt.Println("GOMAXPROCS:", runtime.GOMAXPROCS(0))

	// ----------------------------------------------------------
	// 闭包陷阱(重要!)
	// ----------------------------------------------------------
	fmt.Println("\n=== 闭包陷阱 ===")

	// 【错误示例】循环变量被共享
	fmt.Println("错误方式:")
	var wg2 sync.WaitGroup
	for i := range 3 {
		wg2.Go(func() {
			fmt.Println("  i =", i) // 可能都打印 3
		})
	}
	wg2.Wait()

	// 【正确示例】通过参数传递
	fmt.Println("正确方式:")
	var wg3 sync.WaitGroup
	for i := range 3 {
		wg3.Add(1)
		go func(n int) {
			defer wg3.Done()
			fmt.Println("  n =", n)
		}(i) // 传递当前值
	}
	wg3.Wait()

	// ----------------------------------------------------------
	// 并发安全问题
	// ----------------------------------------------------------
	fmt.Println("\n=== 并发安全问题 ===")

	// 【错误示例】数据竞争
	counter := 0
	var wg4 sync.WaitGroup

	for range 1000 {
		wg4.Go(func() {
			counter++ // 数据竞争!
		})
	}
	wg4.Wait()
	fmt.Println("不安全计数器:", counter, "(可能小于1000)")

	// 【正确示例】使用 Mutex
	counter2 := 0
	var mu sync.Mutex
	var wg5 sync.WaitGroup

	for range 1000 {
		wg5.Go(func() {
			mu.Lock()
			counter2++
			mu.Unlock()
		})
	}
	wg5.Wait()
	fmt.Println("安全计数器:", counter2)

	main02()
	main03()
}

// ----------------------------------------------------------
// 辅助函数
// ----------------------------------------------------------

func sayHello(name string) {
	fmt.Println("Hello,", name)
}

// ============================================================
//                      重要注意事项
// ============================================================
//
// 1. 【启动语法】
//    go 函数名(参数)
//    go func() { ... }()
//
// 2. 【生命周期】
//    - main 退出时所有 goroutine 终止
//    - 没有父子关系,无法直接"杀死"goroutine
//    - 使用 channel 或 context 来协调
//
// 3. 【WaitGroup 使用】
//    - Add() 在启动 goroutine 前调用
//    - Done() 用 defer 确保调用
//    - Wait() 阻塞等待完成
//
// 4. 【闭包陷阱】
//    循环中启动 goroutine 时,通过参数传递循环变量
//
// 5. 【数据竞争】
//    - 多个 goroutine 访问共享数据需要同步
//    - 使用 Mutex、Channel 或 atomic
//    - go run -race 检测数据竞争
//
// 6. 【goroutine 泄漏】
//    - 确保 goroutine 有退出条件
//    - 阻塞的 goroutine 不会被 GC
//
// 7. 【调度】
//    - Go 使用 M:N 调度(M 个 goroutine 对应 N 个 OS 线程)
//    - GOMAXPROCS 控制并行度(默认等于 CPU 核心数)

💬 讨论

使用 GitHub 账号登录后即可参与讨论

基于 MIT 许可发布