GO:Goroutine

By kcersing , 17 四月, 2026

Goroutine是什么?

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

和线程的区别是什么?

Goroutine 是 Go 语言实现并发的核心,它是在操作系统线程之上的“轻量级线程”。

1. 内存占用 (Memory)

  • Goroutine:非常轻量。启动一个 Goroutine 只需要约 2 KB 的栈内存。且它的栈空间是动态的,可以根据需要自动增长或收缩,最大1G。
  • 线程:相对较重。通常一个系统线程需要预分配 1 MB - 8 MB 的固定栈内存。

2. 创建与切换开销 (Context Switch)

  • Goroutine:在用户态完成切换。由 Go 运行时(Runtime)调度,不需要进入操作系统内核。切换时只需保存极少数寄存器,耗时极短(约 200 纳秒)。
  • 线程:在内核态完成切换。需要操作系统进行上下文切换,涉及大量的寄存器、内存页表等状态的保存与恢复,耗时较长(约 1-10 微秒)。

3. 调度模型 (Scheduling)

  • Goroutine:采用 M:N 调度模型。Go 运行时将 M个 Goroutine 映射到 N个内核线程上。这意味着一个程序可以轻松运行数百万个 Goroutine,而不会耗尽系统资源。
  • 线程:通常是 1:1 映射。一个用户线程对应一个内核线程,受系统资源限制,线程数量增加到一定程度后,性能会因频繁切换而剧烈下降。

4. 生命周期管理

  • Goroutine:没有 ID 概念(不能像线程那样获取 ID 并操作),生命周期完全由 Go 运行时管理。
  • 线程:有明确的 ID,操作系统提供丰富的 API 供用户手动控制线程的优先级、终止等。

总结对比表

特性 Goroutine 操作系统线程
内存占用 极小 (约 2KB) 较大 (1MB+)
栈空间 动态扩容/缩容 固定大小
调度方式 用户态 (Go Runtime) 内核态 (OS Kernel)
切换成本 极低 (约 200ns) 较高 (约 1-10μs)
并发量 数百万级别 数千至一万级别

创建Goroutine


// 普通调用
func Hello() {
	fmt.Print("hello")
}
func main() {
	Hello()

	//Goroutine
	go Hello()

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

	// 主goroutine继续执行
	fmt.Println("Hello from main")

	//多个Goroutine
	for i := 0; i < 5; i++ {
		go func(n int) {
			fmt.Printf("Goroutine %d\n", n)
		}(i)
	}

	//闭包捕获变量
	//传递参数
	for i := 0; i < 5; i++ {
		go func(i int) {
			fmt.Printf("Goroutine %d\n", i)
		}(i)
	}

	// 创建副本
	for i := 0; i < 5; i++ {
		i := i // 创建i的副本
		go func() {
			fmt.Printf("Goroutine %d\n", i)
		}()
	}

	// 禁止main退出,goroutine没机会执行
	done := make(chan bool)

	go func() {
		time.Sleep(time.Second)
		fmt.Println("Goroutine done")
		done <- true
	}()
	<-done //等待完成
	//有始有终。

	//goroutine泄漏
	ch := make(chan int)
	go func() {
		select {
		case val := <-ch:
			fmt.Println("val", val)
		case <-time.After(time.Second):
			fmt.Println("timeout")
		}
	}()

	//等待Goroutine完成
	//WaitGroup
	wg := sync.WaitGroup{}
	for i := 0; i < 5; i++ {
		wg.Add(1) // 计数器+1
		go func() {
			defer wg.Done() // 计数器-1
			fmt.Printf("Goroutine %d\n", i)
		}()
	}
	wg.Wait() //等待计数器归零

	//Channel
	done = make(chan bool)
	go func() {
		fmt.Println("Goroutine done")
		time.Sleep(time.Second)
		done <- true
	}()
	<-done

	// Context
	ctx, cancel := context.WithTimeout(context.Background(), time.Second)
	defer cancel() // 有始有终
	go func() {
		for {
			select {
			case <-ctx.Done():
				return
			default:
				fmt.Println("Working...")
				time.Sleep(200 * time.Millisecond)
			}
		}
	}()
	<-ctx.Done()

	//errgroup.WithContext

	// 创建一个带有上下文的 errgroup.Group 实例
	g, ctx := errgroup.WithContext(context.Background())

	// 使用 g.Go 来启动 goroutine
	g.Go(func() error {
		// 模拟耗时操作
		time.Sleep(2 * time.Second)
		fmt.Println("goroutine 1 finished")
		return nil
	})

	g.Go(func() error {
		// 模拟耗时操作
		time.Sleep(1 * time.Second)
		fmt.Println("goroutine 2 finished")
		return nil
	})

	// 等待所有 goroutine 完成,并处理可能发生的错误
	if err := g.Wait(); err != nil {
		fmt.Println("An error occurred:", err)
	} else {
		fmt.Println("All goroutines completed successfully")
	}


tasks := make([]int, 10)

	//errgroup.SetLimit
	g, ctx := errgroup.WithContext(context.Background())
	g.SetLimit(10)

	for _, item := range tasks {
		item := item
		g.Go(func() error {
			fmt.Printf("processing item %d\n", item)
			// 模拟处理任务
			return nil // 或者返回错误
		})
	}

	if err := g.Wait(); err != nil {
		fmt.Printf("batch failed %s %s", "err", err)
	}

	//x/sync/semaphore
	sem := semaphore.NewWeighted(10)

	for _, item := range tasks {
		// Acquire 会阻塞直到拿到权重,或 ctx 取消
		if err := sem.Acquire(ctx, 1); err != nil {
			break // context 取消了,停止
		}
		go func(it int) {
			defer sem.Release(1)
			fmt.Printf("processing item %d\n", it)
			// 模拟处理任务
			// 处理完成后会自动释放权重
		}(item)
	}

	// 等待所有 goroutine 完成
	err := sem.Acquire(ctx, 10)
	if err != nil {
		return
	}

	// 等待goroutine执行完成
	time.Sleep(time.Second)
}

Goroutine的核心要点:

特性 说明
轻量级 2KB栈空间
高并发 百万级goroutine
简单 go关键字启动
调度 Go运行时自动调度

使用建议:

  • 用WaitGroup等待完成
  • 避免闭包陷阱
  • 控制goroutine数量
  • 使用context控制生命周期
  • 避免goroutine泄漏

记住:

  • 不要通过共享内存来通信,而应该通过通信来共享内存
  • 主goroutine退出,所有goroutine都会终止
  • goroutine很便宜,但不是免费的

 

 

 

 

 

标签