一、协程(Goroutine)

1. 什么是协程?

Go 中的 goroutine 是一种轻量级的线程,由 Go 运行时管理。与操作系统线程相比,goroutine 的创建和销毁开销非常小,一个 Go 程序可以轻松启动成千上万个 goroutine。

2. 如何启动一个协程?

使用 go 关键字即可启动一个 goroutine:

go function()

3. 示例:主线程与协程并发执行

package main
import (
	"fmt"
	"time"
)
func sayHello() {
	fmt.Println("Hello from goroutine!")
}
func main() {
	go sayHello() // 启动一个协程
	fmt.Println("Hello from main!")
	time.Sleep(1 * time.Second) // 等待一下,让协程有机会执行
}

输出可能为:

Hello from main!
Hello from goroutine!

注意:主线程(main 函数)不会等待 goroutine 执行完毕。如果去掉 time.Sleep,可能看不到 goroutine 的输出,因为 main 已经退出了。


二、信号(Channel)

1. 什么是 Channel?

Channel(通道) 是 Go 中用于在多个 goroutine 之间传递数据的“管道”。它可以让一个 goroutine 安全地发送数据,另一个 goroutine 接收数据,从而实现协程间的通信和同步。

2. 如何创建和使用 Channel?

ch := make(chan int) // 创建一个传递 int 类型数据的 channel
ch <- 42             // 发送数据到 channel
value := <-ch        // 从 channel 接收数据

3. 示例:主线程等待协程完成

package main
import "fmt"
func worker(done chan bool) {
	fmt.Println("Working...")
	done <- true // 发送完成信号
}
func main() {
	done := make(chan bool)
	go worker(done)
	<-done // 阻塞,直到接收到信号
	fmt.Println("Worker finished.")
}

输出:

Working...
Worker finished.

三、带缓冲的 Channel

1. 缓冲 Channel 与非缓冲 Channel

  • 非缓冲 Channel:发送和接收必须同时准备好,否则会阻塞。
  • 缓冲 Channel:可以存储一定数量的数据,只有缓冲区满时发送才会阻塞。

2. 示例:缓冲 Channel

package main
import "fmt"
func main() {
	ch := make(chan int, 2) // 缓冲大小为 2
	ch <- 1
	ch <- 2
	fmt.Println(<-ch)
	fmt.Println(<-ch)
}

输出:

1
2

四、使用 select 监听多个 Channel

1. 什么是 select

select 语句可以同时监听多个 channel 的操作,哪个先就绪就执行哪个。它类似于 switch,但用于 channel。

2. 示例:超时控制

package main
import (
	"fmt"
	"time"
)
func main() {
	ch := make(chan string)
	go func() {
		time.Sleep(2 * time.Second)
		ch <- "result"
	}()
	select {
	case res := <-ch:
		fmt.Println("Received:", res)
	case <-time.After(1 * time.Second):
		fmt.Println("Timeout!")
	}
}

输出:

Timeout!

五、使用 sync.WaitGroup 等待多个协程

1. 什么是 WaitGroup

sync.WaitGroup 是一个计数器,用于等待一组 goroutine 完成执行。

2. 示例:启动多个协程并等待全部完成

package main
import (
	"fmt"
	"sync"
	"time"
)
func worker(id int, wg *sync.WaitGroup) {
	defer wg.Done()
	fmt.Printf("Worker %d starting\n", id)
	time.Sleep(time.Second)
	fmt.Printf("Worker %d done\n", id)
}
func main() {
	var wg sync.WaitGroup
	for i := 1; i <= 3; i++ {
		wg.Add(1)
		go worker(i, &wg)
	}
	wg.Wait()
	fmt.Println("All workers finished.")
}

输出可能为:

Worker 1 starting
Worker 2 starting
Worker 3 starting
Worker 1 done
Worker 2 done
Worker 3 done
All workers finished.

六、综合示例:并发任务 + 超时 + 错误处理

下面是一个综合运用 goroutine、channel、select 和 WaitGroup 的例子,模拟并发执行多个任务,超时或出错则整体失败。

package main
import (
	"context"
	"errors"
	"fmt"
	"sync"
	"time"
)
func task(ctx context.Context, name string, duration time.Duration) error {
	select {
	case <-time.After(duration):
		fmt.Printf("%s completed\n", name)
		return nil
	case <-ctx.Done():
		return fmt.Errorf("%s canceled", name)
	}
}
func main() {
	var wg sync.WaitGroup
	errCh := make(chan error, 3)
	ctx, cancel := context.WithTimeout(context.Background(), 2*time.Second)
	defer cancel()
	tasks := []struct {
		name string
		dur  time.Duration
	}{
		{"Task 1", 1 * time.Second},
		{"Task 2", 3 * time.Second},
		{"Task 3", 1 * time.Second},
	}
	for _, t := range tasks {
		wg.Add(1)
		go func(name string, dur time.Duration) {
			defer wg.Done()
			err := task(ctx, name, dur)
			if err != nil {
				errCh <- err
			}
		}(t.name, t.dur)
	}
	go func() {
		wg.Wait()
		close(errCh)
	}()
	for err := range errCh {
		if err != nil {
			fmt.Println("Error:", err)
			return
		}
	}
	fmt.Println("All tasks succeeded.")
}

输出可能为:

Task 1 completed
Task 3 completed
Error: Task 2 canceled

总结

概念 作用 常见用法
goroutine 轻量级并发执行单元 go func()
channel 协程间通信 ch := make(chan int)
select 多路监听 channel select { case <-ch: ... }
WaitGroup 等待一组协程完成 wg.Add(1), wg.Done(), wg.Wait()
context 控制协程生命周期(超时、取消) ctx, cancel := context.WithTimeout(...)

更多推荐