golang channel

一、概念:

1、channel是可以让一个 goroutine 发送特定值到另一个 goroutine 的通信机制,channel 的发送方和接受方是 goroutine 对象,属于内存级别的通信

2、channel 在多并发操作里是属于协程安全的,并且遵循了 FIFO 特性。即先执行读取的 goroutine 会先获取到数据,先发送数据的 goroutine 会先输入数据

特性箴言:

1.给一个 nil channel 发送数据,造成永远阻塞

2.从一个 nil channel 接收数据,造成永远阻塞

3.给一个已经关闭的 channel 发送数据,引起 panic

4.从一个已经关闭的 channel 接收数据,如果缓冲区中为空,则返回一个零值

5.无缓冲的channel是同步的,而有缓冲的channel是非同步的

二、Channel的创建

1
ch := make(chan int)

上面是创建了无缓冲的 channel,一旦有 goroutine 往 channel 发送数据,那么当前的 goroutine 会被阻塞住,直到有其他的 goroutine 消费了 channel 里的数据,才能继续运行

还有另外一种是有缓冲的 channel,它的创建是这样的:

1
ch := make(chan int, 2)

第二个参数表示 channel 可缓冲数据的容量。只要当前 channel 里的元素总数不大于这个可缓冲容量,则当前的 goroutine 就不会被阻塞住

三、channel的读写

往一个channel发送数据,可以这样写

1
2
ch := make(chan int)
ch <- 1

对应的读操作

1
data <- ch

当我们不再使用 channel 的时候,可以对其进行关闭:

1
close(ch)

当 channel 被关闭后,如果继续往里面写数据,则程序会直接 panic 退出。不过读取关闭后的 channel,不会产生 pannic,还是可以读到数据。

如果关闭后的 channel 没有数据可读取时,将得到零值,即对应类型的默认值。

为了能知道当前 channel 是否被关闭,可以使用下面的写法来判断。

1
2
3
if v, ok := <-ch; !ok {
       fmt.Println("channel 已关闭,读取不到数据")
}

四、channel和select

在写程序时,有时并不单单只会和一个 goroutine 通信,当我们要进行多 goroutine 通信时,则会使用 select 写法来管理多个 channel 的通信数据:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
ch1 := make(chan struct{})
    ch2 := make(chan struct{})
 
    // ch1, ch2 发送数据
    go sendCh1(ch1)
    go sendCh1(ch2)
 
    // channel 数据接受处理
    for {
        select {
        case <-ch1:
            doSomething1()
        case <-ch2:
            doSomething2()
        }
    }

五、channel 的 deadlock

前面提到过,往 channel 里读写数据时是有可能被阻塞住的,一旦被阻塞,则需要其他的 goroutine 执行对应的读写操作,才能解除阻塞状态。

然而,阻塞后一直没能发生调度行为,没有可用的 goroutine 可执行,则会一直卡在这个地方,程序就失去执行意义了。此时 Go 就会报 deadlock 错误,如下代码

1
2
3
4
5
6
7
func main() {
       ch := make(chan int)
       <-ch
 
       // 执行后将 panic:
       // fatal error: all goroutines are asleep - deadlock!
   }

因此,在使用 channel 时要注意 goroutine 的一发一取,避免 goroutine 永久阻塞!

六、交替打印ABC

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
package main
 
import (
    "fmt"
    "sync"
)
 
//控制循环次数
var count = 5
 
func main() {
    wg := sync.WaitGroup{}
    chanA := make(chan int, 1)
    chanB := make(chan int, 1)
    chanC := make(chan int, 1)
    chanA <- 1
    wg.Add(3)
    go printA(&wg, chanA, chanB)
    go printB(&wg, chanB, chanC)
    go printC(&wg, chanC, chanA)
    wg.Wait()
}
func printA(wg *sync.WaitGroup, chanA, chanB chan int) {
    defer wg.Done()
    for i := 0; i < count; i++ {
        <-chanA
        fmt.Println("A")
        chanB <- 1
    }
}
func printB(wg *sync.WaitGroup, chanB, chanC chan int) {
    defer wg.Done()
    for i := 0; i < count; i++ {
        <-chanB
        fmt.Println("B")
        chanC <- 1
    }
}
func printC(wg *sync.WaitGroup, chanC, chanA chan int) {
    defer wg.Done()
    for i := 0; i < count; i++ {
        <-chanC
        fmt.Println("C")
        chanA <- 1
    }
}


感谢您的阅读,如果您觉得阅读本文对您有帮助,请点一下“推荐”按钮。本文欢迎各位转载,但是转载文章之后必须在文章页面中给出作者和原文连接
posted @   南昌拌粉的成长  阅读(58)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 震惊!C++程序真的从main开始吗?99%的程序员都答错了
· 【硬核科普】Trae如何「偷看」你的代码?零基础破解AI编程运行原理
· 单元测试从入门到精通
· 上周热点回顾(3.3-3.9)
· winform 绘制太阳,地球,月球 运作规律
点击右上角即可分享
微信分享提示