• golang学习笔记---channel(2)


    channel容量为0和为1的区别

    • 容量为1的channel是有缓冲channel的特殊情况,可以用在2个goroutine之间同步状态,或者其中一个等待另一个完成时才继续执行任务的情况。
    • 无缓存的channel的容量始终为0,发送者发送数据和接受者接受数据时同时的,无任何中间态,不能缓冲任何数据。
    • 容量为1的channel是可以缓冲1个数据,发送者和接受者之间可以不同时进行,可以发送者可以先把数据放进去,接受者可以过会儿再读取数据。无缓存的 channel 的发送者和接受者是相互等待,发送者等待接受者准备就绪才能发送数据,接受者等待发送者准备就绪才能接受数据,如果无缓存的 channel 在同一个协程中既发送又接受就会造成死锁而报错。

    使用Range来遍历channel
    使用for range来遍历channel,会自动等待channel的操作,一直到channel被关闭,退出循环。
    第一个协程发送完数据之后关闭channel,使用range遍历读取channel中的数据,当channel被关闭后会退出循环结束程序

    package main
    
    import (
    	"fmt"
    	"time"
    )
    
    func main() {
    	//缓冲容量为3的channel
    	c := make(chan int64, 3)
    	//在协程中向channel中写数据
    	go func() {
    		for i := 0; i < 10; i++ {
    			time.Sleep(time.Microsecond * 100)
    			c <- time.Now().UnixNano()
    		}
    		close(c)
    	}()
    
    	//通过range来打印数据,直到channel被关闭
    	go func() {
    
    		for i := range c {
    			time.Sleep(time.Microsecond * 1000)
    			fmt.Println(i)
    		}
    	}()
    
    	fmt.Scanln() //使用了fmt.Scanln()通过控制台输入扫描来hold住控制台,不让程序退出
    	fmt.Println("完成")
    }
    

      

    关闭Channel
    关闭channel使用了内建的函数close,对于关闭channel需要注意如下几点:

    • 如果向已经关闭的channel写数据就会导致panic: send on closed channel
    • 从已经关闭的channel中读取数据是不会导致panic的,可以继续读取已经发送的数据,但如果已经发送的数据读取完成时继续读取,就会会读取到类型默认值或者零值;
    • 如果通过range读取数据,channel关闭后就会跳出for循环;
    • 如果重复再关闭已经关闭的channel,也会导致panic。

    select

    • select 可以等待和处理多个通道。使用select可以在case语句中选择一组channel中未阻塞的channel。
    • select只会执行一次不会循环,只会选择一个case来处理,如果要一直处理channel,通常要结合一个无限for循环一起来使用
    • 在default case存在的情况下,如果没有case需要处理,则会选择default去处理;如果没有default case,则select语句会阻塞,直到某个case需要处理。
    • 如果在使用for 无限循环+select来操作多个channel,当channel被关闭后,会一直读取类型默认值,这样会导致进入无限死循环
    package main
    
    import (
    	"fmt"
    	"time"
    )
    
    func main() {
    	//创建2个channel
    	c1 := make(chan string)
    	c2 := make(chan string)
    	//通过2个协程分别往2个channel中写数据
    	go func() {
    		for i := 0; i < 9; i++ {
    			time.Sleep(100 * time.Millisecond)
    			c1 <- fmt.Sprintf("c1: %d", i+1)
    		}
    		close(c1)
    
    	}()
    	go func() {
    		for i := 0; i < 5; i++ {
    			time.Sleep(100 * time.Millisecond)
    			c2 <- fmt.Sprintf("c2: %d", i+1)
    		}
    		close(c2)
    	}()
    	//通过1个协程从2个channel中读取数据,如果没有数据则阻塞
    	go func() {
    		for {
    			select { // 死循环
    			case msg1 := <-c1:
    				fmt.Printf("received %d: %s 
    ", time.Now().Unix(), msg1)
    			case msg2 := <-c2:
    				fmt.Printf("received %d: %s 
    ", time.Now().Unix(), msg2)
    			}
    		}
    	}()
    
    	fmt.Scanln()
    
    }
    

     对于读取已经关闭的channel时,可以使用返回值来判断channel是否被关闭,下面的例子中如果返回的ok为false,就说明channel已经被关闭: 

    Select超时
    在select中可以处理超时,超时在处理外部资源或需要绑定执行时间的程序非常重要,通过channel和select,在Go中可以很容易且优雅的实现超时机制。在下面的例子使用一个协程来模拟任务处理,利用sleep 5秒钟来模拟任务执行时间,任务完成后向channel中写入结果;在select语句中实现超时,第一个case来读取channel中的数据,等待结果写入;第二个channel中使用time.After(3 * time.Second)来等待3秒超时时间。由于实际任务执行时间是5秒钟,超时时间是3秒钟,所以等待3秒钟后,time.After返回的channel中会写入一个时间,select语句就选择第二个case执行,然后结束程序:

    package main
    
    import "time"
    import "fmt"
    
    func main() {
    
    	c1 := make(chan string, 1)
    	go func() {
    		fmt.Println("开始时间", time.Now().Unix())
    		time.Sleep(5 * time.Second)
    		c1 <- "result 1"
    	}()
    
    	select {
    	case res := <-c1:
    		fmt.Println(res)
    	case <-time.After(3 * time.Second):
    		fmt.Println("timeout 3")
    	}
    	fmt.Println("完成时间:", time.Now().Unix())
    
    }

     输出:

    开始时间 1595297994

    timeout 3

    完成时间: 1595297997

  • 相关阅读:
    关于发现宇宙微波背景(CMB)辐射的一则趣闻
    windows 8,关闭随意窗体都提示“已停止工作”的解决的方法
    非洲小孩
    Android自己定义控件背景及其Drawable以实现扁平化
    POJ2533:Longest Ordered Subsequence
    iOS Dev (63) 怎样在 TableView 滚动时收起键盘?
    自己用c语言做的日历
    time .h 的用法
    动态规划--目标和问题
    Linux shell编程学习笔记---第八章
  • 原文地址:https://www.cnblogs.com/saryli/p/13353271.html
Copyright © 2020-2023  润新知