15.GO-管道

管道

管道是经常被设计的概念,特别是C语言,在进场间通讯中非常常用,在Go中更多用在协程间通讯

在多个goroutine并发中,我们不仅可以通过原子函数和互斥锁保证对共享资源的安全访问,消除竞争的状态,还可以通过使用管道,在多个goroutine发送和接受共享的数据,达到数据同步的目的。

管道,他有点像在两个routine之间架设的管道,一个goroutine可以往这个管道里塞数据,另外一个可以从这个管道里取数据,有点类似于我们说的队列。

//声明一个通道很简单,我们使用chan关键字即可,除此之外,还要指定通道中发送和接收数据的类型,这样我们才能知道,要发送什么类型的数据给通道,也知道从这个通道里可以接收到什么类型的数据。
//通道类型和Map这些类型一样,可以使用内置的make函数声明初始化,这里我们初始化了一个chan int类型的通道,所以我们只能往这个通道里发送int类型的数据,当然接收也只能是int类型的数据。
ch:=make(chan int)

管道使用

//看例子,慢慢理解发送和接收的用法。发送操作<-在通道的后面,看箭头方向,表示把数值2发送到通道ch里;接收操作<-在通道的前面,而且是一个一元操作符,看箭头方向,表示从通道ch里读取数据。读取的数据可以赋值给一个变量,也可以忽略。
ch <- 2 //发送数值2给这个通道
x:=<-ch //从通道里读取值,并把读取的值赋值给x变量
<-ch //从通道里读取值,然后忽略

通道我们还可以使用内置的close函数关闭。

func main() {
      ch := make(chan int)
      //如果一个通道被关闭了,我们就不能往这个通道里发送数据了,如果发送的话,会引起painc异常。但是,我们还可以接收通道里的数据,如果通道里没有数据的话,接收的数据是零值。
     close(ch)
     ch <- 1
   }
 //打印结果  
///private/var/folders/w9/qkyh3gtx0jvdphr97dvpndyw0000gn/T/___go_build_test2_go
//panic: send on closed channel
//goroutine 1 [running]:

无缓冲的管道

无缓冲的通道指的是通道的大小为0,也就是说,这种类型的通道在接收前没有能力保存任何值,它要求发送goroutine和接收goroutine同时准备好,才可以完成发送和接收操作。

从上面无缓冲的通道定义来看,发送goroutine和接收gouroutine必须是同步的,同时准备后,如果没有同时准备好的话,先执行的操作就会阻塞等待,直到另一个相对应的操作准备好为止。这种无缓冲的通道我们也称之为同步通道。

ch:=make(chan int)
ch:=make(chan int,0)

无缓冲管道案例

//在计算sum和的goroutine没有执行完,把值赋给ch通道之前,fmt.Println(<-ch)会一直等待,所以main主goroutine就不会终止,只有当计算和的goroutine完成后,并且发送到ch通道的操作准备好后,同时<-ch就会接收计算好的值,然后打印出来。
func main() {
    ch := make(chan int)
    go func() {
        fmt.Println("协程开始处理!")
        var sum int = 0
        for i := 0; i < 10; i++ {
            sum += i
        }
        fmt.Println(0)
        time.Sleep(time.Second * 4)
        fmt.Println("开始往管道插入数据")
        ch <- sum
        fmt.Println(11111)
    }()
    fmt.Println(2222)
    //<-ch 会被阻塞,知道  ch <- sum 这个操作完成,否则会一直阻塞
    fmt.Println(<-ch)
    fmt.Println(33333)
}
//返回数据
2222
协程开始处理!
0
开始往管道插入数据
11111
45
33333

有缓冲的通道

有缓冲通道,其实是一个队列,这个队列的最大容量就是我们使用make函数创建通道时,通过第二个参数指定的。

//这里创建容量为3的,有缓冲的通道。对于有缓冲的通道,向其发送操作就是向队列的尾部插入元素,接收操作则是从队列的头部删除元素,并返回这个刚刚删除的元素。
ch := make(chan int, 3)

当队列满的时候,发送操作会阻塞;当队列空的时候,接受操作会阻塞。有缓冲的通道,不要求发送和接收操作时同步的,相反可以解耦发送和接收操作。

//想知道通道的容量以及里面有几个元素数据怎么办?其实和map一样,使用cap和len函数就可以了
cap(ch)
len(ch)

管道容量无数据时

func main() {
    ch := make(chan int,1)
    go func() {
        fmt.Println("协程开始处理!")
        var sum int = 45
        fmt.Println(0)
        time.Sleep(time.Second * 4)
        fmt.Println("开始往管道插入数据")
        ch <- sum
        fmt.Println(11111)
    }()
    fmt.Println(2222)
    //虽然管道容量是1,但是因为管道里面没数据,所以还是会阻塞的,一直等到  ch <- sum 写入数据才会执行  <-ch
    fmt.Println(<-ch)
    fmt.Println(33333)
}
// 返回
2222
协程开始处理!
0
开始往管道插入数据
11111
45
33333

管道有数据时

func main() {
    ch := make(chan int,1)
    ch <- 78
    go func() {
        fmt.Println("协程开始处理!")
        var sum int = 45
        fmt.Println(0)
        time.Sleep(time.Second * 4)
        fmt.Println("开始往管道插入数据")
        ch <- sum
        fmt.Println(11111)
    }()
    fmt.Println(2222)
    //因为管道中有数据,所以不需要等待 ch <- sum 操作,也就不需要阻塞了
    fmt.Println(<-ch)
    fmt.Println(33333)
}
//结果
2222
78
33333

管道插入的数据超过容量

func main() {
    ch := make(chan int,1)
    ch <- 78
    //管道插入的数据不能超过容量本身,否者会报错的
    ch <- 56
}
//返回
fatal error: all goroutines are asleep - deadlock!

单行管道

有时候,我们有一些特殊场景,比如限制一个通道只可以接收,但是不能发送;有时候限制一个通道只能发送,但是不能接收,这种通道我们称为单向通道(半双工管道)。

定义单向通道

//只能发送
var send chan<- int 
//只能接收
var receive <-chan int 

单向通道应用于函数或者方法的参数比较多

//例子这样的,只能进行发送操作,防止误操作,使用了接收操作,如果使用了接收操作,在编译的时候就会报错的。
func counter(out chan<- int) {
}

额外注意

案例1.没有协程的场景

    c2 := make(chan os.Signal,1)
    //这是因为在“<-c2”处,期望从管道中拿到数据,而这个数据必须是其他goroutine协程放入管道的。但是这里并没有其他goroutine,那么就永远不会有数据放入管道。所以,main goroutine线在等一个永远不会来的数据,那整个程序就永远等下去了,所以程序down掉。
    s2 := <-c2
    fmt.Println("退出信号", s2)
    os.Exit(200)

//返回
fatal error: all goroutines are asleep - deadlock!

案例2.main有写入数据时

c1 := make(chan os.Signal,1)
    c1 <- syscall.SIGINT
    s1 := <-c1
    fmt.Println("退出信号", s1)
    os.Exit(200)
//返回
退出信号 interrupt

案例3

    c := make(chan os.Signal,1)
    //当执行“go func”时,协程一直在循环处理,一直有协程在工作,通道一直在等待协程往里放数据,所有不会down掉。
    go func() {
        time.Sleep(time.Second * 5)
        fmt.Println("开始执行 syscall.SIGINT")
        c <- syscall.SIGINT
        fmt.Println("结束执行 syscall.SIGINT")
    }()
    s := <-c
    fmt.Println("退出信号", s)
    os.Exit(200)
//返回
开始执行 syscall.SIGINT
结束执行 syscall.SIGINT
退出信号 interrupt

案例4

    c2 := make(chan os.Signal,1)
    //监听所有信号,main有这个函数
    signal.Notify(c2)
    //main有Notify这个函数,那么 <-c2 就会阻塞
    s2 := <-c2
    fmt.Println("退出信号", s2)
    os.Exit(200)
//返回
^C退出信号 interrupt

案例5

    ints := make(chan string,3)
    go func() {
        fmt.Println(1111)
        //因为管道里面没有数据,所以这里会一直阻塞,不会执行 fmt.Println("aaa" + a)
        a := <-ints
        fmt.Println("aaa" + a)
    }()
    time.Sleep(time.Second * 3)
    fmt.Println(333333)
    os.Exit(2000)
//返回
1111
333333
本作品采用《CC 协议》,转载必须注明作者和本文链接
good good study day day up
讨论数量: 0
(= ̄ω ̄=)··· 暂无内容!

讨论应以学习和精进为目的。请勿发布不友善或者负能量的内容,与人为善,比聪明更重要!