
go语言以其内置的并发原语——goroutine和channel——极大地简化了并发编程。goroutine是轻量级的执行线程,由go运行时管理,而channel则是goroutine之间进行通信和同步的管道。这种“通过通信共享内存而非通过共享内存通信”的设计哲学,使得并发程序的编写更加安全和直观。
在复杂的并发场景中,我们经常需要从多个独立的并发源收集数据并将其聚合到一个单一的通道中进行处理。这种模式被称为“通道复用”(Fan-In)。fanIn函数是实现这一模式的典型示例,它接收多个输入通道,并启动独立的goroutine将每个输入通道的数据转发到一个新的输出通道。
考虑以下boring函数,它模拟了一个持续发送消息的并发源,每个消息之间伴随随机延迟:
package main
import (
"fmt"
"time"
"math/rand"
)
// boring函数模拟一个并发消息生产者
func boring(msg string) <-chan string {
c := make(chan string)
go func() { // 启动一个goroutine发送消息
for i := 0; ; i++ {
c <- fmt.Sprintf("%s %d", msg, i)
time.Sleep(time.Duration(rand.Intn(1e3)) * time.Millisecond) // 随机延迟
}
}()
return c
}
// fanIn函数将两个输入通道的消息复用到一个输出通道
func fanIn(input1, input2 <-chan string) <-chan string {
c := make(chan string)
go func() { for { c <- <-input1 } }() // 从input1读取并转发
go func() { for { c <- <-input2 } }() // 从input2读取并转发
return c
}在main函数中,我们创建两个boring实例("Joe"和"Ann"),并通过fanIn函数将它们的输出聚合。然后,我们从聚合通道中读取消息:
func main() {
c := fanIn(boring("Joe"), boring("Ann"))
for i := 0; i < 10; i++ { // 尝试读取10条消息
fmt.Println(<-c)
}
fmt.Printf("You're both boring, I'm leaving...\n")
}当运行上述代码时,我们可能会观察到以下输出:
立即学习“go语言免费学习笔记(深入)”;
Joe 0 Ann 0 Joe 1 Ann 1 Joe 2 Ann 2 Joe 3 Ann 3 Joe 4 Ann 4 You're both boring, I'm leaving...
这种现象被称为“锁步”(lock-step),即尽管我们期望"Joe"和"Ann"的消息能够异步交错出现,但它们却似乎同步地一对一对出现。这可能会让人误以为并发机制没有按预期工作。
然而,这里的关键在于并发的非确定性。Go调度器在多个goroutine之间切换,并且time.Sleep(time.Duration(rand.Intn(1e3)) * time.Millisecond)引入了随机延迟。在程序启动初期,或者当读取消息的次数较少时(例如本例中的10次),即使存在随机延迟,也可能因为Go调度器的行为、系统负载或随机数生成器初期的值,导致两个goroutine在短时间内表现出近似同步的行为。
并发性意味着可以独立执行,但不保证执行顺序或精确的时间交错。在短时间内,两个独立的goroutine可能恰好以相似的节奏生成并发送消息,尤其是在随机延迟的范围允许这种“巧合”发生时。
要真正观察到并发带来的异步和非确定性,我们需要给程序足够的运行时间,让随机延迟的效果充分累积并显现出来。只需简单地增加从聚合通道读取消息的次数,例如从10次增加到20次:
func main() {
c := fanIn(boring("Joe"), boring("Ann"))
for i := 0; i < 20; i++ { // 增加读取次数,例如到20次
fmt.Println(<-c)
}
fmt.Printf("You're both boring, I'm leaving...\n")
}重新运行程序,我们更有可能看到以下类型的输出,其中消息不再严格地一对一出现,而是展现出明显的交错和异步性:
Joe 0 Ann 0 Joe 1 Ann 1 Joe 2 Ann 2 Joe 3 Ann 3 Joe 4 Ann 4 Joe 5 Ann 5 Joe 6 Ann 6 Ann 7 // Ann的消息提前了 Joe 7 Joe 8 Joe 9 Ann 8 Ann 9 // Ann的消息滞后了
这个输出清晰地表明,"Ann"和"Joe"的消息不再严格同步,而是根据它们各自的随机延迟在聚合通道中交错。
理解Go语言并发的非确定性是掌握其强大功能的基础。通过耐心观察和适当的测试,我们可以更好地理解和利用goroutine和channel来构建高效、健壮的并发应用程序。
以上就是深入理解Go语言并发:通道复用与非确定性行为的观察的详细内容,更多请关注php中文网其它相关文章!
每个人都需要一台速度更快、更稳定的 PC。随着时间的推移,垃圾文件、旧注册表数据和不必要的后台进程会占用资源并降低性能。幸运的是,许多工具可以让 Windows 保持平稳运行。
Copyright 2014-2025 https://www.php.cn/ All Rights Reserved | php.cn | 湘ICP备2023035733号