Go 并发可视化解释 — 通道

当谈到并发时,许多编程语言采用共享内存/状态模型。然而,Go 通过实现通信顺序进程 (CSP) 来区别自己。在CSP中,一个程序由并行进程组成,这些进程不共享状态;相反,它们使用通道来通信并同步它们的行为。

因此,对于有兴趣采用 Go 的开发者来说,理解通道的工作原理变得至关重要。在这篇文章中,我将使用 Gophers 运行他们的想象咖啡店的有趣类比来描述通道,因为我坚信人们更容易通过视觉来学习。

场景

Partier、Candier 和 Stringer 正在经营一个咖啡馆。由于制作咖啡所需的时间比接受订单要长,Partier 会协助接受顾客的订单,然后将这些订单传递给厨房,Candier 和 Stringer 在那里准备咖啡。

无缓冲通道

最初,咖啡店以最简单的方式运作:每当收到一个新订单时,Partier 将订单放入通道,并等待 Candier 或 Stringer 拿走它,然后再接受任何新订单。通过无缓冲通道,使用 ch := make(chan Order) 创建,实现了 Partier 和厨房之间的这种沟通。当通道中没有挂起的订单时,即使 Stringer 和 Candier 都准备好接受新订单,他们仍然处于等待状态,等待新订单到来。

当收到一个新订单时,Partier 将其放入通道,使该订单可以被 Candier 或 Stringer 拿走。但在接受新订单之前,Partier 必须等待其中一个人从通道中取出订单。

当 Stringer 和 Candier 都准备好接受新订单时,新订单将立即被其中一个拿走。然而,不能保证或预测到底是谁会拿到订单。Stringer 和 Candier 之间的选择是不确定的,这取决于诸如调度和 Go 运行时的内部机制之类的因素。假设 Candier 得到了这个第一个订单。

在 Candier 完成处理第一个订单后,她回到等待状态。如果没有新的订单到来,两个工人,Candier 和 Stringer,都会闲置,直到 Partier 将另一个订单放入通道供他们处理。

当收到一个新订单,且 Stringer 和 Candier 都可以处理它时。即使 Candier 刚刚处理了上一个订单,接收新订单的特定工人仍然是不确定的。在这个场景中,我们假设 Candier 再次被分配了这第二个订单。

新的订单 order3 到来,由于 Candier 正在处理 order2,她并没有等在 order := <-ch 这一行,Stringer 成为了唯一可以接收 order3 的可用工人。因此,他会得到它。

order3 发送给 Stringer 之后,order4 立即到达。此时,Stringer 和 Candier 都已经在处理他们各自的订单,没有人可以拿走 order4。因为通道没有缓冲,将 order4 放入通道会阻塞 Partier,直到 Stringer 或 Candier 有一个变得可以接受 order4。我经常看到人们对无缓冲通道(用 make(chan order)make(chan order, 0) 创建)和缓冲大小为1的通道(用 make(chan order, 1) 创建)感到困惑。因此,他们错误地期望 ch <- order4 立即完成,允许 Partier 接受 order5,然后在 ch <- order5 上被阻塞。如果你也有这种想法,我在 Go Playground 上创建了一个代码片段,帮助你纠正误解 https://go.dev/play/p/shRNiDDJYB4

缓冲通道

无缓冲通道确实可以工作,但它限制了整体的吞吐量。如果他们仅接受一定数量的订单在后台(厨房)顺序处理会更好。这可以通过缓冲通道来实现。现在,即使 Stringer 和 Candier 忙于处理他们的订单,只要通道没有满,Partier 仍然可以在通道中留下新的订单并继续接受其他订单,例如,最多3个挂起的订单。

通过引入缓冲通道,咖啡店增强了处理更多订单的能力。但是,选择适当的缓冲大小以保持合理的客户等待时间是至关重要的。毕竟,没有客户愿意忍受过长的等待时间。有时,拒绝新订单可能比接受它们并且无法及时完成它们更为可取。此外,在短暂的容器化(Docker)应用程序中使用缓冲通道时必须小心,因为预期会有随机重启,在这种情况下,从通道中恢复消息可能是一项具有挑战性的任务,甚至可能是不可能的。

通道与阻塞队列

尽管它们在本质上是不同的,但Java中的Blocking Queue是用来在线程之间通信的,而Go中的Channel是用于Goroutine的通信,BlockingQueue 和 Channel 的行为在某种程度上是相似的。如果你熟悉BlockingQueue,那么理解Channel肯定会很容易。

常见用途

通道是Go应用中的一个基础且广泛使用的功能,服务于各种目的。通道的一些常见用途包括:

  • Goroutine 通信:通道使不同的goroutines之间能够进行消息交换,使它们可以协作而无需直接共享状态。
  • 工作池:正如上面的示例中所见,通道经常用于管理工作池,其中多个相同的工作人员从共享通道处理传入的任务。
  • 扇出,扇入:通道促进了扇出,扇入模式,其中多个goroutines(扇出)执行工作并将结果发送到一个通道,而另一个goroutine(扇入)消费这些结果。
  • 超时和截止日期:与 select 语句结合使用,通道可以用于处理超时和截止日期,确保程序可以优雅地处理延迟并避免无限的等待。

我将在其他文章中更详细地探讨通道的不同用途。但是,现在,让我们通过实现上述的咖啡店场景来结束这篇入门博客,并亲眼看到通道如何在实践中工作。我们将探索Partier、Candier和Stringer之间的互动,并观察通道如何促进他们之间的顺畅沟通和协调,使咖啡店能够有效地处理订单和同步。

给我看你的代码!

package main

import (
    "fmt"
    "log"
    "math/rand"
    "sync"
    "time"
)

func main() {
    ch := make(chan order, 3)

    wg := &sync.WaitGroup{} // More on WaitGroup another day
    wg.Add(2)

    go func() {
        defer wg.Done()
        worker("Candier", ch)
    }()

    go func() {
        defer wg.Done()
        worker("Stringer", ch)
    }()

    for i := 0; i < 10; i++ {
        waitForOrders()
        o := order(i)
        log.Printf("Partier: I %v, I will pass it to the channel\n", o)
        ch <- o
    }

    log.Println("No more orders, closing the channel to signify workers to stop")
    close(ch)

    log.Println("Wait for workers to gracefully stop")
    wg.Wait()

    log.Println("All done")
}

func waitForOrders() {
    processingTime := time.Duration(rand.Intn(2)) * time.Second
    time.Sleep(processingTime)
}

func worker(name string, ch <-chan order) {
    for o := range ch {
        log.Printf("%s: I got %v, I will process it\n", name, o)
        processOrder(o)
        log.Printf("%s: I completed %v, I'm ready to take a new order\n", name, o)
    }
    log.Printf("%s: I'm done\n", name)
}

func processOrder(_ order) {
    processingTime := time.Duration(2+rand.Intn(2)) * time.Second
    time.Sleep(processingTime)
}

type order int

func (o order) String() string {
    return fmt.Sprintf("order-%02d", o)
}

您可以复制这段代码,在您的IDE上进行调整并运行它,以更好地理解通道是如何工作的。

如果您对保持对软件工程领域的最新动态感兴趣,请关注我。我将确保让您了解最新信息!

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 223,652评论 6 521
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 95,678评论 3 401
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 170,645评论 0 366
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 60,483评论 1 300
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 69,503评论 6 399
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 53,039评论 1 314
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 41,420评论 3 427
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 40,391评论 0 278
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 46,921评论 1 322
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 38,973评论 3 343
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 41,114评论 1 354
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 36,761评论 5 350
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 42,437评论 3 336
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 32,928评论 0 25
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 34,042评论 1 275
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 49,596评论 3 380
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 46,142评论 2 363

推荐阅读更多精彩内容