Go 并发编程实战课
晁岳攀(鸟窝)
前微博技术专家,知名微服务框架 rpcx 作者
25635 人已学习
新⼈⾸单¥59
登录后,你可以任选4讲全文学习
课程目录
已完结/共 22 讲
Go 并发编程实战课
15
15
1.0x
00:00/00:00
登录|注册

13 | Channel:另辟蹊径,解决并发问题

panic和goroutine泄漏
close
recv
send
初始化
chan数据结构
其它操作
接收数据
发送数据
任务编排
信号通知
数据传递
数据交流
Go语言设计
CSP模型
思考题
总结
踩过的坑
容易犯的错误
实现原理
基本用法
应用场景
发展
Channel

该思维导图由 AI 生成,仅供参考

你好,我是鸟窝。
Channel 是 Go 语言内建的 first-class 类型,也是 Go 语言与众不同的特性之一。Go 语言的 Channel 设计精巧简单,以至于也有人用其它语言编写了类似 Go 风格的 Channel 库,比如docker/libchantylertreat/chan,但是并不像 Go 语言一样把 Channel 内置到了语言规范中。从这一点,你也可以看出来,Channel 的地位在编程语言中的地位之高,比较罕见。
所以,这节课,我们就来学习下 Channel。

Channel 的发展

要想了解 Channel 这种 Go 编程语言中的特有的数据结构,我们要追溯到 CSP 模型,学习一下它的历史,以及它对 Go 创始人设计 Channel 类型的影响。
CSP 是 Communicating Sequential Process 的简称,中文直译为通信顺序进程,或者叫做交换信息的循序进程,是用来描述并发系统中进行交互的一种模式。
CSP 最早出现于计算机科学家 Tony Hoare 在 1978 年发表的论文中(你可能不熟悉 Tony Hoare 这个名字,但是你一定很熟悉排序算法中的 Quicksort 算法,他就是 Quicksort 算法的作者,图灵奖的获得者)。最初,论文中提出的 CSP 版本在本质上不是一种进程演算,而是一种并发编程语言,但之后又经过了一系列的改进,最终发展并精炼出 CSP 的理论。CSP 允许使用进程组件来描述系统,它们独立运行,并且只通过消息传递的方式通信。
确认放弃笔记?
放弃后所记笔记将不保留。
新功能上线,你的历史笔记已初始化为私密笔记,是否一键批量公开?
批量公开的笔记不会为你同步至部落
公开
同步至部落
取消
完成
0/2000
荧光笔
直线
曲线
笔记
复制
AI
  • 深入了解
  • 翻译
    • 英语
    • 中文简体
    • 中文繁体
    • 法语
    • 德语
    • 日语
    • 韩语
    • 俄语
    • 西班牙语
    • 阿拉伯语
  • 解释
  • 总结

Go语言中的Channel是一种独特的并发编程工具,实现了Go语言的CSP模型,通过通信的方式实现并发处理,避免了共享内存的复杂性。本文从Channel的发展、应用场景和基本用法三个方面介绍了Channel的特点和用法。首先,文章介绍了CSP模型的历史和对Go语言设计Channel类型的影响,以及Channel在并发编程中的优势。其次,文章详细阐述了Channel的应用场景,包括数据交流、数据传递、信号通知、任务编排和锁等五种类型。最后,文章介绍了Channel的基本用法,包括发送数据、接收数据以及其他操作,如close、cap、len等。通过对Channel类型的实现原理的分析,读者可以更好地理解和应用Channel。文章通过对Channel的底层实现和操作方法的分析,使读者能够更深入地理解Channel的功能和异常情况,为读者提供了一份全面的Channel学习指南。 此外,文章还总结了使用Channel容易犯的错误,包括panic和goroutine泄漏,以及解决这些错误的方法。作者提供了一套选择的方法,建议在共享资源的并发访问时使用传统并发原语,在复杂的任务编排和消息传递时使用Channel。同时,还围观了知名Go项目的Channel相关Bug,对Bug进行了分析和修复。最后,文章提出了思考题,引发读者思考和讨论。 总的来说,本文深入浅出地介绍了Go语言中Channel的特性和用法,对于想要了解并发编程的读者具有很高的参考价值。

仅可试看部分内容,如需阅读全部内容,请付费购买文章所属专栏
《Go 并发编程实战课》
新⼈⾸单¥59
立即购买
登录 后留言

全部留言(61)

  • 最新
  • 精选
  • Noir
    package main import "fmt" import "time" func main() { chArr := [4]chan struct{} { make(chan struct{}), make(chan struct{}), make(chan struct{}), make(chan struct{}), } for i := 0; i < 4; i++ { go func(i int) { for { <- chArr[i % 4] fmt.Printf("i am %d\n", i) time.Sleep(1 * time.Second) chArr[(i + 1) % 4] <- struct{}{} } }(i) } chArr[0] <- struct{}{} select{} }

    作者回复: 对

    2021-12-19
    4
    10
  • 星星之火
    channel 中包含的 mutex 是什么呢,和课程最开始的 sync.mutex 是同一个东西吗? 因为 sync.mutex 是依赖 channel 实现的,感觉应该不是同一个 mutex?

    作者回复: 不是同一个,只是类似。channel中这个是运行时内部使用的mutex

    2020-12-05
    3
    4
  • Geek_43dc82
    我实在是太蠢了,只能写出这样的代码了 package main import "fmt" func main() { signChan1 := make(chan struct{}) signChan2 := make(chan struct{}) signChan3 := make(chan struct{}) signChan4 := make(chan struct{}) mainSignChan := make(chan struct{}) for i := 1; i <= 4; i++ { go func(i int) { for { select { case <-signChan1: fmt.Println(1) signChan2 <- struct{}{} case <-signChan2: fmt.Println(2) signChan3 <- struct{}{} case <-signChan3: fmt.Println(3) signChan4 <- struct{}{} case <-signChan4: fmt.Println(4) signChan1 <- struct{}{} } } }(i) } signChan1 <- struct{}{} <-mainSignChan }

    作者回复: 写的不错,简单直接

    2022-04-19
    3
    2
  • Goroutine 泄漏的那个例子,如果把 unbuffered chan 改成容量为 1 的 buffered chan,那么假如函数超时了,子 goroutine 也能够往 channel 中发送数据。那么 GC 会把这个 channel 回收吗?

    作者回复: 会

    2021-04-27
    2
  • 老猫
    func chgoroutine(in,out,stop chan struct{},n int) { for{ select{ case <-in: fmt.Println(n) time.Sleep(time.Second) out <-struct{}{} case <-stop: return } } } func main() { ch1 := make(chan struct{}, 0) ch2 := make(chan struct{},0) ch3 := make(chan struct{},0) ch4 := make(chan struct{},0) stop := make(chan struct{},0) go chgoroutine(ch1,ch2,stop,1) go chgoroutine(ch2,ch3,stop,2) go chgoroutine(ch3,ch4,stop,3) go chgoroutine(ch4,ch1,stop,4) ch1 <-struct{}{} time.Sleep(time.Second * 20) stop <-struct{}{} }

    作者回复: 😃

    2022-01-23
    1
  • huizhou92
    func main() { wg := sync.WaitGroup{} ctx, cancelFunc := context.WithCancel(context.Background()) f := func(wg *sync.WaitGroup, index int, req, resp chan struct{}) { defer wg.Done() for { select { case _ = <-req: fmt.Println(fmt.Sprintf("Hello, World!%d", index)) time.Sleep(time.Second * 1) resp <- struct{}{} case <-ctx.Done(): return } } } chain := make([]chan struct{}, 4) for i := 0; i < 4; i++ { chain[i] = make(chan struct{}, 1) } wg.Add(4) for i := 0; i < 4; i++ { go f(&wg, i+1, chain[i], chain[(i+1)%4]) } chain[0] <- struct{}{} <-time.After(time.Second * 20) cancelFunc() wg.Wait() }

    作者回复: 👍

    2024-03-01归属地:北京
  • chimission
    package main import ( "fmt" "time" ) func printChan(c chan int) { st := <-c fmt.Println(st%4 + 1) time.Sleep(1 * time.Second) c <- st + 1 go printChan(c) } func main() { ch := make(chan int, 4) ch <- 0 printChan(ch) select {} }

    作者回复: 基本上,你这个写法还是串行的实现

    2023-01-07归属地:北京
  • 清风
    func main() { chArr := []chan struct{}{ make(chan struct{}), make(chan struct{}), make(chan struct{}), make(chan struct{}), make(chan struct{}), make(chan struct{}), } for k, _ := range chArr { if k == len(chArr)-1 { go goon(chArr[k], chArr[0], k+1) } else { go goon(chArr[k], chArr[k+1], k+1) } } chArr[0] <- struct{}{} select {} } func goon(ch chan struct{}, ch2 chan struct{}, index int) { time.Sleep(time.Duration(index*10) * time.Millisecond) for { <-ch fmt.Printf("I am No %d Goroutine\n", index) time.Sleep(time.Second) ch2 <- struct{}{} } }

    作者回复: OK

    2022-10-19归属地:北京
  • 张觥
    func TestChannel1Practice(t *testing.T) { var ch = make(chan struct{}) wg := sync.WaitGroup{} wg.Add(4) go func() { ch <- struct{}{} }() for thread := 1; thread <= 4; thread++ { go func(thead int) { _, ok := <-ch if ok { for i := 1; i <= 4; i++ { t.Logf("%d: %d", thead, i) time.Sleep(1 * time.Second) } wg.Done() ch <- struct{}{} } }(thread) } wg.Wait() t.Log("finished") }

    作者回复: 你这样并不是每个goroutine打印自己的id

    2022-10-18归属地:北京
  • 草色青青
    func tt(ctx context.Context, c1, c2 *chan int) { for { select { case n := <-*c1: fmt.Println(n) nn := n + 1 if n == 4 { nn = 1 } *c2 <- nn //fmt.Printf("c1:%p,c2:%p\n", c1, c2) case <-ctx.Done(): return } } } func PrintInfo() { ctx, cancel := context.WithCancel(context.Background()) c1, c2, c3, c4 := make(chan int, 2), make(chan int, 2), make(chan int, 2), make(chan int, 2) fmt.Printf("c1:%p,c2:%p,c3:%p,c4:%p\n", &c1, &c2, &c3, &c4) go tt(ctx, &c1, &c2) go tt(ctx, &c2, &c3) go tt(ctx, &c3, &c4) go tt(ctx, &c4, &c1) c1 <- 1 fmt.Println("Hello, 世界") time.Sleep(time.Millisecond * 70) cancel() fmt.Println("Hello, 世界") }

    作者回复: 👍🏻

    2022-09-18归属地:北京
收起评论
显示
设置
留言
61
收藏
沉浸
阅读
分享
手机端
快捷键
回顶部