编程入门
Java 入门教程:从零开始学 Java
零基础学 JavaScript 异步编程:从回调到 async/await
Rust 所有权与借用机制入门
Go 语言零基础教程 - 并发编程入门
Go 语言入门教程:并发编程基础(Goroutine 与 Channel)
Python 零基础入门:从第一行代码到独立编程
本站点使用 MrDoc 构建
-
+
Go 语言零基础教程 - 并发编程入门
> 分类:入门教程 | 语言:Go | 难度:⭐⭐ 初级  ## 学习目标 学完本文你将能够: - 理解**并发**与**并行**的区别 - 使用 `go` 关键字启动 Goroutine - 通过 Channel 在 Goroutine 之间安全通信 - 使用 `select` 实现多路复用 - 避免常见的并发陷阱(竞态条件、死锁) ## 前置知识 - Go 语言基础语法(变量、函数、控制流) - 了解基本的数据类型 - 已安装 Go 1.20+(可在终端运行 `go version` 验证) 如果你还没安装 Go,访问 [https://go.dev/dl/](https://go.dev/dl/) 下载安装。 ## 核心概念一:Goroutine — 轻量级线程 ### 什么是 Goroutine? Goroutine 是 Go 运行时管理的轻量级执行单元。与操作系统线程相比: | 特性 | Goroutine | OS Thread | |------|-----------|-----------| | 初始栈大小 | ~2KB | ~1MB | | 创建开销 | 微秒级 | 毫秒级 | | 调度方式 | Go 运行时协作式调度 | 操作系统抢占式调度 | | 数量上限 | 轻松创建百万个 | 通常几千个就到极限 |  ### 启动一个 Goroutine 只需要在函数调用前加 `go` 关键字: ```go package main import ( "fmt" "time" ) func sayHello(name string) { for i := 0; i < 3; i++ { fmt.Printf("Hello from %s! (count: %d)\n", name, i) time.Sleep(100 * time.Millisecond) } } func main() { // 启动一个 Goroutine go sayHello("Goroutine") // 主 Goroutine sayHello("Main") // 注意:如果 main 退出了,所有 Goroutine 都会被终止 // 所以需要等待或者使用 sync.WaitGroup } ``` ### 使用 sync.WaitGroup 等待 ```go package main import ( "fmt" "sync" "time" ) func worker(id int, wg *sync.WaitGroup) { defer wg.Done() // 完成后通知 WaitGroup fmt.Printf("Worker %d starting\n", id) time.Sleep(time.Duration(id) * 100 * time.Millisecond) fmt.Printf("Worker %d done\n", id) } func main() { var wg sync.WaitGroup for i := 1; i <= 5; i++ { wg.Add(1) // 增加计数器 go worker(i, &wg) } wg.Wait() // 等待所有 Goroutine 完成 fmt.Println("All workers done!") } ``` **关键要点:** - `wg.Add(1)` 在启动 Goroutine **之前**调用 - `defer wg.Done()` 确保函数退出时通知 WaitGroup - `wg.Wait()` 阻塞直到计数器归零 ## 核心概念二:Channel — 通信管道 ### Don't communicate by sharing memory; share memory by communicating. 这是 Go 并发哲学的核心。Channel 就是实现这一理念的机制。  ### 创建和使用 Channel ```go package main import "fmt" func main() { // 创建无缓冲 Channel ch := make(chan string) // 启动发送方 go func() { ch <- "Hello from Goroutine!" // 发送数据 }() // 接收数据 msg := <-ch fmt.Println(msg) } ``` ### 无缓冲 vs 带缓冲 Channel ```go // 无缓冲:发送方阻塞直到有人接收 unbuffered := make(chan int) // 带缓冲:缓冲区满之前发送不阻塞 buffered := make(chan int, 3) buffered <- 1 // 不阻塞 buffered <- 2 // 不阻塞 buffered <- 3 // 不阻塞 // buffered <- 4 // 会阻塞!缓冲区已满 ``` ### Channel 方向 Go 允许你限制 Channel 的方向,提高类型安全: ```go // 只发送 Channel func sender(ch chan<- int) { ch <- 42 } // 只接收 Channel func receiver(ch <-chan int) { msg := <-ch fmt.Println(msg) } ``` ### 使用 select 实现多路复用 `select` 让你同时等待多个 Channel 操作: ```go package main import ( "fmt" "time" ) func main() { ch1 := make(chan string) ch2 := make(chan string) go func() { time.Sleep(100 * time.Millisecond) ch1 <- "result from ch1" }() go func() { time.Sleep(50 * time.Millisecond) ch2 <- "result from ch2" }() // select 会选择已就绪的那个 Channel select { case msg1 := <-ch1: fmt.Println(msg1) case msg2 := <-ch2: fmt.Println(msg2) } } ``` ### 超时处理 ```go select { case result := <-ch: fmt.Println("Got result:", result) case <-time.After(3 * time.Second): fmt.Println("Timeout! Operation took too long.") } ``` ### 关闭 Channel 和 range 遍历 ```go package main import "fmt" func fibonacci(n int, ch chan<- int) { x, y := 0, 1 for i := 0; i < n; i++ { ch <- x x, y = y, x+y } close(ch) // 关闭 Channel,告诉接收方没有更多数据 } func main() { ch := make(chan int) go fibonacci(10, ch) // range 会自动在 Channel 关闭时退出 for num := range ch { fmt.Println(num) } } ``` ## 动手练习 ### 练习 1:并发下载模拟 编写一个程序,模拟 3 个并发下载任务,使用 WaitGroup 等待所有下载完成: ```go package main import ( "fmt" "math/rand" "sync" "time" ) func download(url string, wg *sync.WaitGroup) { defer wg.Done() duration := time.Duration(rand.Intn(3)+1) * time.Second fmt.Printf("Downloading %s ...\n", url) time.Sleep(duration) fmt.Printf("Done: %s (took %v)\n", url, duration) } func main() { rand.Seed(time.Now().UnixNano()) var wg sync.WaitGroup urls := []string{"https://example.com/a", "https://example.com/b", "https://example.com/c"} for _, url := range urls { wg.Add(1) go download(url, &wg) } wg.Wait() fmt.Println("All downloads complete!") } ``` ### 练习 2:生产者-消费者模式 使用带缓冲 Channel 实现生产者-消费者模式: ```go package main import "fmt" func producer(ch chan<- int) { for i := 1; i <= 5; i++ { ch <- i fmt.Printf("Produced: %d\n", i) } close(ch) } func consumer(ch <-chan int, done chan<- bool) { for num := range ch { fmt.Printf("Consumed: %d\n", num) } done <- true } func main() { ch := make(chan int, 3) // 带缓冲 done := make(chan bool) go producer(ch) go consumer(ch, done) <-done fmt.Println("Producer-Consumer finished!") } ``` ### 练习 3:超时限速器 使用 `select` + `time.After` 实现一个限时操作: > 提示:创建一个 Channel,启动 Goroutine 模拟耗时操作,用 select 同时等待结果和超时。 <details> <summary>点击查看参考答案</summary> ```go package main import ( "fmt" "time" ) func slowOperation(ch chan<- string) { time.Sleep(2 * time.Second) ch <- "operation result" } func main() { ch := make(chan string) go slowOperation(ch) select { case result := <-ch: fmt.Println("Success:", result) case <-time.After(1 * time.Second): fmt.Println("Timeout! Operation too slow.") } } ``` </details> ## 常见新手误区 ### ❌ 误区 1:Goroutine 中直接使用闭包变量 ```go // 错误写法!所有 Goroutine 可能打印相同的值 for i := 0; i < 5; i++ { go func() { fmt.Println(i) // 可能全部打印 5 }() } // 正确写法:通过参数传递 for i := 0; i < 5; i++ { go func(n int) { fmt.Println(n) }(i) } ``` ### ❌ 误区 2:忘记等待 Goroutine ```go // main 函数退出了,Goroutine 也会被终止 func main() { go func() { time.Sleep(1 * time.Second) fmt.Println("这行可能永远不会打印") }() // main 直接退出! } ``` ### ❌ 误区 3:向已关闭的 Channel 发送数据 ```go ch := make(chan int) close(ch) ch <- 1 // panic: send on closed channel! // 正确做法:只有发送方应该关闭 Channel // 接收方用 range 遍历或检查第二个返回值 val, ok := <-ch if !ok { fmt.Println("Channel is closed") } ``` ### ❌ 误区 4:竞态条件 ```go // 多个 Goroutine 同时修改共享变量 → 数据竞争! var counter int var wg sync.WaitGroup for i := 0; i < 1000; i++ { wg.Add(1) go func() { counter++ // 竞态条件! wg.Done() }() } wg.Wait() fmt.Println(counter) // 可能不是 1000 // 解决方案:使用 sync.Mutex 或 atomic 或 Channel var mu sync.Mutex // 在修改前加锁 mu.Lock() counter++ mu.Unlock() ``` 运行 `go run -race main.go` 可以检测竞态条件! ## 下一步 恭喜完成 Go 并发编程入门!接下来可以探索: 1. **sync 包**:`Mutex`、`RWMutex`、`Once`、`Cond` 等同步原语 2. **context 包**:超时控制、取消信号传播(Go 并发的精华) 3. **并发模式**:Worker Pool、Pipeline、Fan-out/Fan-in 4. **errgroup**:带错误处理的并发任务组 5. **实战项目**:写一个并发爬虫或聊天服务器 > 💡 推荐阅读: > - [Go 官方博客 - Go Concurrency Patterns](https://go.dev/blog/pipelines) > - [Effective Go - Concurrency](https://go.dev/doc/effective_go#concurrency) > - 书籍《Concurrency in Go》
admin
2026年5月17日 01:13
转发文档
收藏文档
上一篇
下一篇
手机扫码
复制链接
手机扫一扫转发分享
复制链接
Markdown文件
PDF文档(打印)
分享
链接
类型
密码
更新密码