go并发编程
Table of Contents
以下是一份详细的 Go 语言并发编程学习文档,涵盖核心概念、语法、模式及最佳实践
1. 并发与并行基础
- 并发:逻辑上的同时处理多任务(单核切换)。
- 并行:物理上的同时执行多任务(多核)。
- Go 的并发模型基于 CSP(Communicating Sequential Processes),核心思想:通过通信共享内存,而非通过共享内存来通信。
2. Goroutine
- 轻量级线程:由 Go 运行时管理,开销极小(KB 级栈内存)。
- 启动方式:使用
go
关键字。go func() { fmt.Println("Hello from goroutine!") }()
- 特点:
- 非阻塞式启动。
- 无返回值(需通过 channel 传递结果)。
- 主协程退出,所有子协程立即终止。
3. Channel
- 类型化管道:用于协程间通信,声明语法
chan T
。 - 创建方式:
ch := make(chan int) // 无缓冲通道 bufferedCh := make(chan string, 5) // 缓冲通道(容量5)
- 操作:
- 发送:
ch <- data
- 接收:
data := <-ch
- 关闭:
close(ch)
(发送方关闭,接收方可检测到)
- 发送:
- 无缓冲通道:同步操作,发送接收必须同时就绪。
- 缓冲通道:异步操作,缓冲区满时发送阻塞。
4. 同步机制
4.1 sync.WaitGroup
- 等待一组协程完成。
var wg sync.WaitGroup wg.Add(3) // 添加3个任务 for i := 0; i < 3; i++ { go func() { defer wg.Done() // 任务逻辑 }() } wg.Wait() // 阻塞直到所有任务完成
4.2 sync.Mutex
与 sync.RWMutex
- 互斥锁保护共享资源:
var counter int var mu sync.Mutex func increment() { mu.Lock() defer mu.Unlock() counter++ }
RWMutex
:读写分离锁,适合读多写少场景。
4.3 sync.Once
- 确保代码仅执行一次:
var once sync.Once once.Do(func() { // 初始化代码(线程安全) })
4.4 sync.Cond
-
条件变量,用于协程间通知:
cond := sync.NewCond(&mu) // 等待条件 cond.L.Lock() for !condition { cond.Wait() } // 满足条件后操作 cond.L.Unlock() // 通知其他协程 cond.Broadcast()
5. Select 语句
- 多路复用通道操作:
select { case msg1 := <-ch1: fmt.Println(msg1) case msg2 := <-ch2: fmt.Println(msg2) case <-time.After(1 * time.Second): fmt.Println("timeout") default: fmt.Println("no activity") }
- 特性:
- 随机选择就绪的 case。
- 可用于超时控制、非阻塞操作。
6. Context 包
-
管理协程生命周期、传递请求级数据。
-
核心方法:
context.Background()
:根上下文。context.WithCancel()
:创建可取消的上下文。context.WithTimeout()
:设置超时。
-
示例:
ctx, cancel := context.WithTimeout(context.Background(), 2*time.Second) defer cancel() go func(ctx context.Context) { select { case <-ctx.Done(): fmt.Println("任务取消或超时") return case <-time.After(3 * time.Second): fmt.Println("任务完成") } }(ctx)
7. 并发模式
7.1 Worker Pool
-
固定数量的工作协程处理任务队列:
jobs := make(chan int, 100) results := make(chan int, 100) // 启动3个worker for w := 1; w <= 3; w++ { go worker(w, jobs, results) } // 分发任务 for j := 1; j <= 10; j++ { jobs <- j } close(jobs) // 收集结果 for a := 1; a <= 10; a++ { <-results }
7.2 扇入(Fan-In)
- 合并多个通道数据:
func fanIn(input1, input2 <-chan string) <-chan string { ch := make(chan string) go func() { for { ch <- <-input1 } }() go func() { for { ch <- <-input2 } }() return ch }
7.3 扇出(Fan-Out)
- 一个通道分发给多个协程:
func fanOut(source <-chan int, n int) []<-chan int { outputs := make([]<-chan int, n) for i := 0; i < n; i++ { ch := make(chan int) go func() { defer close(ch) for val := range source { ch <- val } }() outputs[i] = ch } return outputs }
8. 常见陷阱与解决方案
- 竞态条件:使用
-race
标志检测:go run -race main.go
- 死锁:确保所有协程都有机会释放锁。
- Goroutine 泄漏:始终确保协程能退出。
- 通道阻塞:合理使用缓冲或
select
的default
分支。
9. 性能优化
- 控制并发数量:使用带缓冲的 channel 或 worker pool。
- 减少锁竞争:
- 缩小临界区范围。
- 使用原子操作
sync/atomic
。
- 避免频繁创建协程:复用协程(如池化技术)。
10. 实战案例
并发 Web 爬虫
func crawl(url string, depth int, wg *sync.WaitGroup) {
defer wg.Done()
// 抓取逻辑...
}
func main() {
var wg sync.WaitGroup
urls := []string{"http://example.com"}
for _, u := range urls {
wg.Add(1)
go crawl(u, 3, &wg)
}
wg.Wait()
}
通过系统学习以上内容,结合实践项目,可逐步掌握 Go 语言强大的并发编程能力。建议从简单案例入手,逐步深入理解 CSP 模型的设计哲学。
明天再详细了解一下 go 操作数据库与提供 API