zoukankan      html  css  js  c++  java
  • channel

    一.基本语法

    c := make(chan bool) //创建一个无缓冲的bool型Channel
    c <- x        //向一个Channel发送一个值
    <- c          //从一个Channel中接收一个值
    x = <- c      //从Channel c接收一个值并将其存储到x中
    x, ok = <- c  //从Channel接收一个值,如果channel关闭了或没有数据,那么ok将被置为false
    ch chan int //可读写
    ch1 chan<- int  //ch1只能写
    ch2 <-chan int  //ch2只能读
    channel是类型相关的,也就是一个channel只能传递一种类型
    

      

    二.为什么要使用channel

    goroutine是Go语言中的轻量级线程实现,由Go运行时(runtime)管理.
    先看一个例子:

    func Sub(i int) {
    	fmt.Println("from sub func", i)
    }
    
    func main() {
    	for i := 0; i < 5; i++ {
    		Sub(i)
    	}
    	fmt.Println("from main")
    }
    

    这个例子做了一件事情,在main函数中串行执行了5次Sub函数.

    如果我们需要Sub函数能够并发的执行,我们加个go,将每一个Sub函数放在goroutine中去(main函数其实也是个goroutine),代码如下所示:

    func Sub(i int) {
    	fmt.Println("from sub func", i)
    }
    
    func main() {
    	for i := 0; i < 5; i++ {
    		go Sub(i)
    	}
    	fmt.Println("from main")
    }
    

      

    编译执行,你会发现只打印出了from main,Sub函数中字符并没有打印出来.这是因为主函数main启动了5个Sub函数后,并没有等待它们完成即退出了!这显然不是我们要的结果,我们使用go提供的消息通信机制channel来重构代码,保证Sub函数执行完成后,主函数再退出!

    三.无缓存channel(信道,这个翻译较为接近其英文表达的意思)

    channel(信道)是什么,简单说,是goroutine之间互相通讯的东西。类似我们Unix上的管道(可以在进程间传递消息), 用来goroutine之间发消息和接收消息。其实,就是在做goroutine之间的内存共享。

    func Sub(ch chan int) {
    	for i := 0; i < 5; i++ {
    		fmt.Println("from sub func", i)
    	}
    	ch <- 1 // 向channel存消息,如果没有其他goroutine取走数据,那么挂起Sub
    }
    
    func main() {
    	ch := make(chan int) // 创建了一个无缓存的channel
    	go Sub(ch) // 开启一个goroutine来执行Sub
    	fmt.Println("from main")
    	<-ch // 从channel取消息,如果没有写入消息,挂起main
    }
    

      

    无缓冲的信道在取消息和存消息的时候都会挂起当前的goroutine.

    所以上面代码的执行流程是:
    创建ch信道
    新开goroutine来执行Sub,但因为ch信道中写入的消息没有被取走,Sub挂起.
    from main被打印出来.
    <-ch 挂起了main,直到取到数据,这就保证了Sub完成后,main才结束.

    所以无缓存信道,存入数据必须取走,只存不取或取空的数据,都将导致死锁.

    以下情形都是死锁:

    //取不存在的消息:
    ch := make(chan int)
    <-ch
    
    //只写不取:
    ch := make(chan int)
    ch <- 1
    
    //多个channel,ch1等待ch2的消息,但ch2没有消息写入:
    ch1 := make(chan int)
    ch2 := make(chan int)
    ch1 <- <-ch2
    <-ch1
    

      

    四.带缓存的channel

    无缓存信道只负责流通消息,任何对该信道的读和写,都阻塞信道.
    带缓存的channel,不仅可以流通数据,还可以缓存数据,只有达到缓存最大数目后,也就是缓存满了后,才阻塞信道,这听起来很像队列(Queue).其实,缓冲信道是先进先出的,我们可以把缓冲信道看作为一个线程安全的队列

    ch := make(chan int, 2) // 带缓存channel,缓存数目2个,放入2个数据,不会挂起,只有放入第3个的时候才挂起当前goroutine

    示例代码:

    func main() {
    	ch := make(chan int, 3)
    	ch <- 1
    	ch <- 2
    	ch <- 3
    	fmt.Println(<-ch)
    	fmt.Println(<-ch)
    	fmt.Println(<-ch)
    }
    

      

    对ch的写入,因为没有超过缓存数目3,所以不会阻塞;对ch取数据,将按先进先出,依次输出1 2 3

    你会发现,带缓存的信道,取数据还是挺麻烦的.我们用for range来取数据

    func main() {
    	ch := make(chan int, 3)
    	ch <- 1
    	ch <- 2
    	ch <- 4
    
    	for v := range ch {
    		fmt.Println(v)
    	}
    }
    

      

    报出deadlock,原因是ch没有关闭的情形下,range一直在读取.加一个判断:

    func main() {
    	ch := make(chan int, 3)
    	ch <- 1
    	ch <- 2
    	ch <- 4
    
    	for v := range ch {
    		fmt.Println(v)
    		if len(ch) <= 0 {
    			break
    		}
    	}
    }
    

      

    我们判断了ch中有没有数据,没有数据就跳出循环.可以正常输出.当然我们也可以显式关闭信道.

    func main() {
    	ch := make(chan int, 3)
    	ch <- 1
    	ch <- 2
    	ch <- 4
    
    	close(ch) // 关闭信道
    
    	for v := range ch {
    		fmt.Println(v)
    	}
    }
    

      

    五.执行多个goroutine

    1.无缓存

    const MAX = 1000
    
    var ch chan int
    
    func Sub(i int) {
    	fmt.Println(i)
    	ch <- 1
    }
    
    func main() {
    	ch = make(chan int)
    	for i := 0; i < MAX; i++ {
    		go Sub(i)
    	}
    
    	for i := 0; i < MAX; i++ {
    		<-ch
    	}
    }
    

      

    2.带缓存

    const MAX = 1000
    
    var ch chan int
    
    func Sub(i int) {
    	fmt.Println(i)
    	ch <- 1
    }
    
    func main() {
    	ch = make(chan int, MAX)
    	for i := 0; i < MAX; i++ {
    		go Sub(i)
    	}
    
    	for i := 0; i < MAX; i++ {
    		<-ch
    	}
    }
    

      

    两者效果相同,不同点在于:
    无缓冲的信道是一批数据一个一个的流进流出
    缓冲信道则是一个一个存储,然后一起流出去

    六.单向channel

    顾名思义,单向channel只能用于发送或者接收数据。channel本身必然是同时支持读写的,否则根本没法用。假如一个channel真的只能读,那么肯定只会是空的,因为你没机会往里面写数据。同理,如果一个channel只允许写,即使写进去了,也没有丝毫意义,因为没有机会读取里面的数据。所谓的单向channel概念,其实只是对channel的一种使用限制。

    ch1 chan<- int //ch1只能写
    ch2 <-chan int //ch2只能读

    示例:
    func Parse(ch <-chan int) {
    	for value := range ch {
    		fmt.Println("Parsing value", value)
    	}
    }
    

      

  • 相关阅读:
    第2章 创建基础框架
    目录
    工厂方法模式(Factory Method)
    petshop4.0 详解之七(PetShop表示层设计)
    第1章 启动电子商务网站
    第3章 创建商品目录:第Ⅰ部分
    编写一个JAVA应用程序,用户从键盘只能输入整数,程序输出这些整数的乘积
    书上例题练习第一章
    java与C/C++的区别
    安装JDK遇到的问题
  • 原文地址:https://www.cnblogs.com/itfenqing/p/7634211.html
Copyright © 2011-2022 走看看