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

原文地址:https://www.cnblogs.com/saryli/p/13353271.html