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.Mutexsync.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 泄漏:始终确保协程能退出。
  • 通道阻塞:合理使用缓冲或 selectdefault 分支。

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