go goroutine chan 用法

发布于:2025-08-01 ⋅ 阅读:(21) ⋅ 点赞:(0)

方法1

代码

package main

import (
    "fmt"
    "sync"
    "time"
)

func main() {
    allChan := make(chan interface{}, 3)
    var sendWg, recvWg sync.WaitGroup // 分别同步发送和接收

    // 发送goroutine
    sendWg.Add(1)
    go func() {
        defer sendWg.Done()
        for i := 0; i < 10; i++ {
            time.Sleep(time.Millisecond * 10)
            allChan <- i
            fmt.Printf("发送: %d\n", i)
        }
        close(allChan) // 发送完成后关闭channel
    }()

    // 接收goroutine
    recvWg.Add(1)
    go func() {
        defer recvWg.Done()
        for item := range allChan {
            time.Sleep(time.Millisecond * 20) // 模拟处理耗时
            fmt.Printf("接收: %v\n", item)
        }
    }()

    // 先等待发送完成,再等待接收完成
    sendWg.Wait()
    recvWg.Wait()
    fmt.Println("所有操作完成")
}

运行结果:

标准输出:发送: 0
发送: 1
接收: 0
发送: 2
发送: 3
接收: 1
发送: 4
发送: 5
接收: 2
发送: 6
接收: 3
发送: 7
接收: 4
发送: 8
接收: 5
发送: 9
接收: 6
接收: 7
接收: 8
接收: 9
所有操作完成

分析

优化说明

  1. 双重同步:
  • 使用两个WaitGroup(sendWg和recvWg),分别等待发送和接收 goroutine 完成。
  • 先通过sendWg.Wait()确保所有数据发送完毕并关闭 channel;
  • 再通过recvWg.Wait()确保接收 goroutine 处理完所有数据。
  1. channel 关闭逻辑:
  • 发送完成后关闭 channel 是关键,这会让接收 goroutine 的range循环在取完数据后自动退出,避免接收端阻塞。
  1. 适用场景:
  • 这种 “发送 + 接收都异步” 的模式适合需要主程序同时处理其他逻辑的场景(例如同时监控多个任务),但必须做好同步,否则会出现数据丢失。

方法2

代码

package main

import (
    "fmt"
    "time"
)

func main() {
    // 1. 数据通道:传递实际数据
    dataChan := make(chan interface{}, 3)
    // 2. 发送完成信号通道:发送方结束后通知
    sendDone := make(chan struct{})
    // 3. 接收完成信号通道:接收方结束后通知
    recvDone := make(chan struct{})

    // 发送goroutine
    go func() {
        for i := 0; i < 10; i++ {
            time.Sleep(time.Millisecond * 10)
            dataChan <- i
            fmt.Printf("发送: %d\n", i)
        }
        close(dataChan)       // 发送完毕,关闭数据通道(通知接收方结束)
        sendDone <- struct{}{} // 发送完成信号
    }()

    // 接收goroutine
    go func() {
        for item := range dataChan { // 当dataChan关闭且数据取完后,循环自动退出
            time.Sleep(time.Millisecond * 20)
            fmt.Printf("接收: %v\n", item)
        }
        recvDone <- struct{}{} // 接收完成信号
    }()

    // 主程序等待:先等发送完成,再等接收完成
    <-sendDone  // 阻塞等待发送完成
    <-recvDone  // 阻塞等待接收完成
    fmt.Println("所有操作完成")
}

运行结果

标准输出:发送: 0
发送: 1
接收: 0
发送: 2
发送: 3
接收: 1
发送: 4
发送: 5
接收: 2
发送: 6
接收: 3
发送: 7
接收: 4
发送: 8
接收: 5
发送: 9
接收: 6
接收: 7
接收: 8
接收: 9
所有操作完成

那种更好呢

一、效率对比

sync.WaitGroup 略占优势

  1. sync.WaitGroup 是 Go 标准库专门为 “等待一组 goroutine 完成” 设计的同步原语,底层通过原子操作(计数器加减)实现,几乎没有额外开销,执行效率极高。
  2. 它的逻辑简单:Add 增加计数、Done 减少计数、Wait 阻塞直到计数归零,整个过程不涉及复杂的 runtime 调度交互。
    多通道同步的开销稍高
  3. 通道同步依赖于 channel 的发送 / 接收操作,这些操作需要与 Go runtime 的调度器交互(如检查通道状态、唤醒阻塞的 goroutine 等),理论上比原子操作多一些微小的开销。
  4. 额外的信号通道(如 sendDone、recvDone)会占用少量内存(每个通道需要维护内部数据结构),但在实际应用中影响可忽略。

二、实用性与适用场景

sync.WaitGroup 更适合大多数场景

  1. 代码简洁:对于 “等待多个 goroutine 完成” 的场景,WaitGroup 的语义更直观(Add/Done/Wait 直接对应 “注册 - 完成 - 等待” 逻辑),可读性更高。
  2. 扩展性好:如果需要等待多个发送者或接收者(如 10 个发送 goroutine),WaitGroup 只需一次 Add(10) 即可,无需额外定义多个信号通道。
  3. 通用性强:是 Go 社区的 “标准做法”,团队协作时更容易被理解。
    多通道同步适合特定场景
  4. 符合 Go 哲学:更贴合 “用通信实现共享内存” 的 Go 设计思想,通过 channel 传递信号比直接操作计数器更 “Go 式”。
  5. 灵活扩展:如果需要更复杂的同步逻辑(如 “先等待 A 完成,再启动 B,最后等待 B 完成”),通道的阻塞特性可以天然实现流程控制,而 WaitGroup 可能需要配合额外逻辑。
  6. 无依赖:不依赖 sync 包,纯靠语言原生特性实现,适合理解 channel 机制的场景。

三、总结建议

优先选 sync.WaitGroup:在大多数业务代码中,它更简洁、高效、易维护,是同步 goroutine 的 “标准答案”。
选多通道同步:当你需要强调 “通信优先” 的设计,或同步逻辑较复杂(需要通过通道传递更多状态)时,它是更优雅的选择。

本质区别:

WaitGroup 是 “共享状态” 式同步(通过计数器),多通道是 “通信” 式同步(通过信号传递)。两者效率差异微小,选择时主要看代码可读性和场景适配性。


网站公告

今日签到

点亮在社区的每一天
去签到