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 协议》,转载必须注明作者和本文链接
 
           董雷 的个人博客
 董雷 的个人博客
         
             
           
           关于 LearnKu
                关于 LearnKu
               
                     
                     
                     粤公网安备 44030502004330号
 粤公网安备 44030502004330号 
 
推荐文章: