无缓冲通道

是指在接收前没有能力保存任何值得通道。
这种类型的通道要求发送goroutine和接收goroutine同时准备好,才能完成发送和接收操作。如果两个goroutine没有同时准备好,通道会导致先执行发送或接收操作的goroutine阻塞等待。
这种对通道进行发送和接收的交互行为本身就是同步的,其中任意一个操作都无法离开另一个操作单独存在。

Golang并发编程有缓冲通道和无缓冲通道(channel)_Go开发

上图所示,如同接力赛。根据图编号观察①两个协程,创建好了通道②一个往通道里放,这时候两边阻塞④这时候另一个协程要接⑤另一个协程取出来,从①-⑤都是阻塞的,⑥才完成交接,才不会阻塞。

再比喻: 就是一个送信人去你家门口送信 ,你不在家 他不走,你一定要接下信,他才会走。

无缓冲channel创建

ch := make(chan int, 0)  //第二个参数为0,或者不写第二个参数

如果没有指定缓冲区容量,那么该通道就是同步的,因此会阻塞到发送者准备好发送和接收者准备好接收。

代码案例

package main

import (
    "fmt"
    "time"
)

func main() {
    //创建一个无缓存的channel
    ch := make(chan int, 0)

    //len(ch)缓冲区剩余数据个数, cap(ch)缓冲区大小,两者这里永远都是0
    fmt.Printf("len(ch) = %d, cap(ch)= %d\n", len(ch), cap(ch))

    //新建协程
    go func() {
        for i := 0; i < 3; i++ { //写三次
            fmt.Printf("子协程:i = %d\n", i)
            ch <- i //往chan写内容
            fmt.Printf("len(ch) = %d, cap(ch)= %d\n", len(ch), cap(ch))
        }
    }()

    //延时2秒
    time.Sleep(2 * time.Second)

    for i := 0; i < 3; i++ { //必须读三次
        num := <-ch //读管道中内容,没有内容前,阻塞
        fmt.Println("num = ", num)
    }

}
len(ch) = 0, cap(ch)= 0
子协程:i = 0
num =  0
len(ch) = 0, cap(ch)= 0
子协程:i = 1
len(ch) = 0, cap(ch)= 0
子协程:i = 2
num =  1
num =  2

流程分析

主协程执行到这里,阻塞两秒
//延时2秒
time.Sleep(2 * time.Second)

//两秒钟时间子协程肯定把for循环执行完毕,但这里也会出现阻塞
//阻塞原因是:当执行到往通道写数据是无缓冲的,对方不读之前会阻塞。也就是,在主协程等着子协程写完,但是主协程还没到读的时候,这时候出现阻塞,等到主协程读完数据才会往下走。
可以执行观察一下程序的执行卡顿观察阻塞 for i := 0; i < 3; i++ { //写三次 fmt.Printf("子协程:i = %d\n", i) ch <- i //往chan写内容 fmt.Printf("len(ch) = %d, cap(ch)= %d\n", len(ch), cap(ch)) }

打印结果分析:首先子协程for循环往管道里写入一个数据,紧接着主协程for循环出现阻塞,然后主协程for循环从管道读数据,读完了打印。主协程打印完,子协程for循环继续给管道数据,但也有可能主协程读完数据没来得及打印,子协程就把数据写入管道并打印完毕,因为两个是同时并行的。

有缓冲通道

 指通道可以保存多个值。

如果给定了一个缓冲区容量,那么通道就是异步的,只要缓冲区有未使用空间用于发送数据,或还包含可以接收的数据,那么其通信就会无阻塞地进行

Golang并发编程有缓冲通道和无缓冲通道(channel)_Go开发_02

上图所示:

①右侧的goroutine正在从通道接收一个值。
②右侧的goroutine独立完成了接手值得动作,而左侧的goroutine正在发送一个新值到通道里。
③左侧的goroutine还在向通道发送新值,而右侧的goroutine正在从通道接收另一个值。这个步骤里的两个操作既不是同步,也不会互相阻塞。
④所有的发送和接收都完成,而通道里还有几个值,也有一些空间可以存储更多的值

有缓冲channel创建

ch := make(chan int, 3)  //容量是3

代码案例

package main

import (
    "fmt"
    "time"
)

func main() {
    //创建一个有缓存的channel
    ch := make(chan int, 3) //容量是3

    //len(ch)缓冲区剩余数据个数, cap(ch)缓冲区大小
    fmt.Printf("len(ch) = %d, cap(ch)= %d\n", len(ch), cap(ch))

    //新建协程
    go func() {
        for i := 0; i < 10; i++ { //这里数据量大于管道容量,会出阻塞
            ch <- i //往chan写内容,如果主协程没读的话,写满3个就会阻塞在此
            fmt.Printf("子协程[%d]: len(ch) = %d, cap(ch)= %d\n", i, len(ch), cap(ch))
        }
    }()

    //延时
    time.Sleep(2 * time.Second)

    for i := 0; i < 10; i++ { //这里数据量大于管道容量,会出阻塞
        num := <-ch //读管道中内容,没有内容前,阻塞
        fmt.Println("num = ", num)
    }

}
len(ch) = 0, cap(ch)= 3
子协程[0]: len(ch) = 1, cap(ch)= 3
子协程[1]: len(ch) = 2, cap(ch)= 3
子协程[2]: len(ch) = 3, cap(ch)= 3
num =  0
num =  1
num =  2
num =  3
子协程[3]: len(ch) = 3, cap(ch)= 3
子协程[4]: len(ch) = 0, cap(ch)= 3
子协程[5]: len(ch) = 1, cap(ch)= 3
子协程[6]: len(ch) = 2, cap(ch)= 3
子协程[7]: len(ch) = 3, cap(ch)= 3
num =  4
num =  5
num =  6
num =  7
num =  8
子协程[8]: len(ch) = 3, cap(ch)= 3
子协程[9]: len(ch) = 0, cap(ch)= 3
num =  9

总结一下有缓冲channel和无缓冲channel的特点与不同

无缓冲的与有缓冲channel有着重大差别,那就是一个是同步的 一个是非同步的。

比如
c1:=make(chan int) 无缓冲
c2:=make(chan int,1) 有缓冲
c1<-1 

无缓冲: 不仅仅是向 c1 通道放 1,而是一直要等有别的携程 <-c1 接手了这个参数,那么c1<-1才会继续下去,要不然就一直阻塞着。
有缓冲: c2<-1 则不会阻塞,因为缓冲大小是1(其实是缓冲大小为0),只有当放第二个值的时候,第一个还没被人拿走,这时候才会阻塞。