首页
技术小册
AIGC
面试刷题
技术文章
MAGENTO
云计算
视频课程
源码下载
PDF书籍
「涨薪秘籍」
登录
注册
Go语言的面向对象编程
面向对象编程的本质
Go语言实现封装
Go语言中字段和方法的封装
为值类型和指针类型绑定方法的区别
Go语言实现继承
利用组合实现继承
匿名字段的支持
多继承
Go语言实现多态
面向接口编程
Go语言中的接口
Go语言中的接口实现
利用面向接口编程实现方法多态
编程范例——接口的典型应用
接口嵌套实例
伪继承与接口实现
并发
线程的概念
线程模型
协程的工作原理
协程的使用
GPM模型
从线程模型看GOMAXPROCS参数
Go语言中的协程同步
独占锁——Mutex
读写锁——RWMutex
等待组——WaitGroup
利用channel实现协程同步
利用channel实现锁定
利用channel实现等待组
总结使用channel实现并发控制
让出时间片
time.Sleep()和runtime.Gosched()的本质区别
runtime.Gosched()与多核CPU
Go语言中的单例
利用sync.Once实现单例
sync.Once的实现原理
编程范例——协程池及协程中断
协程池的实现
协程的中断执行
当前位置:
首页>>
技术小册>>
深入浅出Go语言核心编程(四)
小册名称:深入浅出Go语言核心编程(四)
### 章节:利用Channel实现等待组 在Go语言的多线程(或更准确地说,是协程)编程中,协调多个goroutine的执行顺序是一个常见的需求。Go标准库中的`sync.WaitGroup`是处理这类问题的一个强大工具,它允许我们等待一组goroutine的完成。然而,了解如何使用Go的channel机制来实现类似的等待组功能,不仅有助于深入理解Go的并发模型,还能在某些特定场景下提供更加灵活或低开销的解决方案。 #### 1. 理解WaitGroup与Channel 首先,让我们简要回顾一下`sync.WaitGroup`的用法。`WaitGroup`内部维护一个计数器,用于记录待完成的goroutine数量。每当一个goroutine启动时,我们通过调用`WaitGroup`的`Add(1)`方法来增加计数器的值;当goroutine完成时,则调用`Done()`方法(等价于`Add(-1)`),以表示该goroutine已完成其任务。主goroutine通过调用`Wait()`方法阻塞,直到所有注册的goroutine都通过调用`Done()`方法表示它们已完成。 而channel,作为Go语言并发编程的核心,提供了一种在不同goroutine之间安全通信的机制。通过channel的发送(send)和接收(receive)操作,我们可以实现复杂的同步逻辑。 #### 2. 使用Channel实现等待组 利用channel实现等待组的基本思路是:创建一个或多个channel,用于goroutine之间的同步。具体实现方式可能因具体需求而异,但通常包括以下几个步骤: ##### 2.1 计数器Channel 一种简单的方法是利用一个int类型的channel来模拟计数器。每个启动的goroutine都向这个channel发送一个信号(比如,发送一个特定的值,或者简单地关闭channel),主goroutine则等待接收这些信号,直到达到预期的数量。 **示例代码**: ```go package main import ( "fmt" "sync" "time" ) func worker(done chan<- bool, id int) { fmt.Printf("Worker %d starting\n", id) time.Sleep(time.Second) // 模拟工作 fmt.Printf("Worker %d done\n", id) done <- true // 发送完成信号 } func main() { var wg sync.WaitGroup // 使用WaitGroup作为对比 done := make(chan bool, 5) // 假设有5个worker for i := 1; i <= 5; i++ { wg.Add(1) go func(id int) { defer wg.Done() worker(done, id) }(i) } // 等待所有worker完成 go func() { wg.Wait() close(done) // 所有worker完成后关闭channel }() // 另一种等待方式:使用channel for i := 0; i < 5; i++ { <-done // 等待一个worker完成 } fmt.Println("All workers finished") } // 注意:这个示例中同时使用了WaitGroup和channel来展示两种等待机制。 // 在实际应用中,通常会选择其中一种。 ``` **注意**:上述代码为了演示目的同时使用了`sync.WaitGroup`和channel。在实际应用中,通常不需要混合使用,除非有特定的理由。 ##### 2.2 扇入(Fan-in)模式 对于需要等待多个goroutine完成并收集它们结果的场景,扇入模式是一种非常有效的解决方案。Go标准库中的`sync.WaitGroup`可以配合channel的关闭机制来实现这一模式,但直接使用channel也可以完成。 **示例代码**(仅使用channel): ```go func main() { const numWorkers = 5 done := make(chan struct{}, numWorkers) // 使用空结构体作为信号,减少内存占用 results := make(chan int, numWorkers) for i := 1; i <= numWorkers; i++ { go func(id int) { defer func() { done <- struct{}{} }() // 发送完成信号 time.Sleep(time.Second) result := id * 2 // 假设的工作结果 results <- result // 发送结果 }(i) } // 等待所有worker完成 for i := 0; i < numWorkers; i++ { <-done } close(results) // 所有worker完成后关闭结果channel // 收集并打印结果 for result := range results { fmt.Println(result) } } ``` 在这个例子中,我们使用了两个channel:`done`用于同步(即等待所有goroutine完成),而`results`用于收集每个goroutine的执行结果。注意,在关闭`results`之前,我们必须确保所有worker都已经通过`done`channel发送了完成信号,否则可能会有goroutine还在向`results`发送数据时它就已被关闭,导致panic。 #### 3. 性能与适用场景 虽然`sync.WaitGroup`提供了简单且高效的等待组实现,但在某些特定场景下,使用channel可能会更加灵活或高效。例如,当需要等待多个不同的条件同时满足时,或者当goroutine的完成顺序对结果有直接影响时,使用channel可以更方便地实现复杂的同步逻辑。 然而,使用channel也伴随着一定的开销,包括channel的创建、发送和接收操作。因此,在性能敏感的应用中,应当仔细评估不同方案的开销,并选择最适合当前需求的实现方式。 #### 4. 结论 通过本章的学习,我们了解了如何利用Go的channel机制来实现等待组的功能。与`sync.WaitGroup`相比,使用channel提供了更大的灵活性和控制力,但也需要注意其带来的额外开销。在实际开发中,我们应当根据具体需求和环境来选择最合适的同步机制。无论是使用`sync.WaitGroup`还是channel,掌握它们背后的原理和使用方法都是编写高效、可维护的Go并发程序的关键。
上一篇:
利用channel实现锁定
下一篇:
总结使用channel实现并发控制
该分类下的相关小册推荐:
Go Web编程(上)
Go 组件设计与实现
Golang并发编程实战
Go Web编程(中)
Go开发权威指南(上)
深入浅出Go语言核心编程(七)
Go-Web编程实战
Go Web编程(下)
go编程权威指南(三)
Golang修炼指南
Go语言入门实战经典
深入解析go语言