Golang高级语法-channel通信

在 Golang 中,channel 是一种非常重要的语言特性,它可以用来进行并发编程、协程之间的通信以及数据共享。在本文中,我们将介绍 channel 的使用方法和注意事项。

1 什么是 channel?

channel 是一种类型,它可以用来在协程之间传递数据。它类似于 Unix 中的管道,但是比管道更加强大和灵活。使用 channel 可以有效地实现并发编程和协程之间的通信和同步。

在 Golang 中,channel 有两种类型:单向和双向。单向 channel 只能发送或接收数据,不能同时发送和接收。而双向 channel 可以同时发送和接收数据。通常情况下,我们使用双向 channel。

2 channel 的使用方法

2.1 创建 channel

在 Golang 中,可以使用 make 函数创建一个 channel:

ch := make(chan int)

这里创建了一个可以传递 int 类型数据的 channel。

2.2 发送数据

使用 channel 发送数据的方法是使用 <- 运算符:

ch <- 1 // 发送数据 1 到 channel 中

2.3 接收数据

使用 channel 接收数据的方法也是使用 <- 运算符:

x := <-ch // 从 channel 中接收一个值,并赋值给 x

2.4 关闭 channel

在不需要使用 channel 时,可以使用 close 函数关闭 channel:

close(ch)

2.5 非阻塞发送和接收

在有些情况下,我们不希望 channel 的发送和接收操作阻塞,可以使用非阻塞发送和接收:

select {
case ch <- 1:
    // 发送成功
default:
    // 发送失败
}

select {
case x := <-ch:
    // 接收成功
default:
    // 接收失败
}

在以上示例中,使用了 select 语句来实现非阻塞的发送和接收。

2.6 带缓冲的 channel

在创建 channel 时,可以指定缓冲区的大小:

ch := make(chan int, 10) // 创建一个带缓冲区大小为 10 的 channel

当缓冲区未满时,发送操作不会阻塞,而是将数据放入缓冲区。当缓冲区已满时,发送操作会阻塞。

3 channel 注意事项

3.1 不要关闭未初始化的 channel

在使用 channel 之前,一定要先进行初始化。如果关闭未初始化的 channel,会导致运行时 panic。

3.2 不要重复关闭 channel

如果重复关闭 channel,会导致运行时 panic。

3.3 不要在发送方关闭 channel

如果发送方关闭 channel,会导致运行时 panic。应该在接收方关闭 channel。

3.4 不要向已关闭的 channel 发送数据

如果向已关闭的 channel 发送数据,会导致运行时 panic。

3.5 不要从已关闭的 channel 接收数据

如果从已关闭的 channel 接收数据,会返回一个零值,并不会导致 panic。

3.6 channel 的阻塞和死锁

如果发送方发送数据时,channel 已满,或者接收方接收数据时,channel 为空,这时发送方或接收方都会阻塞。如果所有的协程都在等待某个 channel 中的数据,这时会导致死锁。

4 channel 的示例

下面是一个使用 channel 进行并发编程的示例,其中使用了多个协程和 channel 来实现数据共享和通信:

package main

import (
    "fmt"
    "sync"
)

func main() {
    ch := make(chan int)
    var wg sync.WaitGroup
    for i := 0; i < 5; i++ {
        wg.Add(1)
        go func(id int) {
            defer wg.Done()
            ch <- id // 发送数据到 channel
        }(i)
    }
    go func() {
        wg.Wait()
        close(ch) // 关闭 channel
    }()
    for id := range ch {
        fmt.Println(id) // 从 channel 中接收数据并打印
    }
}

在上面的示例中,创建了一个可以传递 int 类型数据的 channel,并使用了 sync.WaitGroup 来等待协程执行完毕。然后使用一个循环来从 channel 中接收数据,并打印出来。

5 总结

在 Golang 中,channel 是一种非常重要的语言特性,它可以用来进行并发编程、协程之间的通信以及数据共享。在使用 channel 时,需要注意避免一些常见的错误,例如不要关闭未初始化的 channel、不要重复关闭 channel 等。同时,也可以使用带缓冲的 channel 和非阻塞的发送和接收来提高代码的性能和可读性。

6 补充:带缓冲的 channel

在上文中,我们介绍了无缓冲的 channel,也就是说,发送操作会一直阻塞,直到接收者接收到数据。除此之外,还有一种带缓冲的 channel,它在创建时可以指定一个缓冲区大小,可以缓存一定数量的元素。当缓冲区满了时,发送操作会被阻塞,直到有接收者接收到元素并腾出缓冲区。

带缓冲的 channel 可以在某些场景下提高并发程序的性能。例如,在生产者-消费者模式中,如果生产者和消费者的处理速度不一致,可以使用一个带缓冲的 channel 来平衡它们之间的速度差异。

下面是一个使用带缓冲的 channel 的示例代码:

package main

import "fmt"

func main() {
    ch := make(chan int, 2) // 创建一个带有两个缓冲区的 channel
    ch <- 1                 // 发送一个元素到 channel 中
    ch <- 2                 // 发送另一个元素到 channel 中
    fmt.Println(<-ch)       // 从 channel 中接收并打印一个元素
    fmt.Println(<-ch)       // 从 channel 中接收并打印另一个元素
}

在上面的示例中,我们创建了一个带有两个缓冲区的 channel,并向其中发送了两个元素。因为缓冲区大小为 2,所以发送操作不会被阻塞。最后,我们从 channel 中接收并打印了这两个元素。

7 补充:select 语句

除了使用无缓冲和带缓冲的 channel,Golang 中还提供了一种在多个 channel 上进行选择的机制,称为 select 语句。select 语句允许程序在多个 channel 上等待数据,直到其中某个 channel 准备好了数据,然后执行对应的 case 语句。

下面是一个使用 select 语句的示例代码:

package main

import "fmt"

func main() {
    ch1 := make(chan int)
    ch2 := make(chan int)
    go func() {
        ch1 <- 1
    }()
    go func() {
        ch2 <- 2
    }()
    select {
    case v1 := <-ch1:
        fmt.Println(v1)
    case v2 := <-ch2:
        fmt.Println(v2)
    }
}

在上面的示例中,我们创建了两个 channel,并在两个协程中向它们分别发送了数据。然后,我们使用 select 语句等待这两个 channel 中的数据,并在其中一个 channel 准备好数据后执行相应的 case 语句。

需要注意的是,如果多个 case 语句都准备好了数据,那么 select 语句会随机选择一个 case 语句执行。如果没有任何一个 case 语句准备好数据,那么 select 语句会被阻塞,直到有一个 case 语句准备好数据。

select 语句也可以与 default 语句结合使用,作为一个备选项,当没有任何一个 case 语句准备好数据时执行 default 语句。

下面是一个使用 select 语句的示例代码,演示了 select 语句的随机选择一个 case 语句的特性:

package main

import (
    "fmt"
    "time"
)

func main() {
    ch1 := make(chan int)
    ch2 := make(chan int)
    go func() {
        time.Sleep(1 * time.Second)
        ch1 <- 1
    }()
    go func() {
        time.Sleep(2 * time.Second)
        ch2 <- 2
    }()
    select {
    case v1 := <-ch1:
        fmt.Println(v1)
    case v2 := <-ch2:
        fmt.Println(v2)
    }
}

在上面的示例中,我们在两个协程中分别向两个 channel 中发送数据,但是在第一个协程中我们加了一个 1 秒的延迟。因此,在执行 select 语句时,由于第一个 case 语句的数据需要等待 1 秒才能准备好,而第二个 case 语句的数据需要等待 2 秒,因此可能随机选择第一个 case 语句执行。

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

推荐阅读更多精彩内容