golang入门day6 (通道 + select)

什么是通道 channel?

”不要通过共享内存来通信 ,而应该通过通信来共享内存“ : 这句话什么意思呢?

举个例子🌰:  如果要把数据传递个另外一个协程,  可以先将数据封装成对象, 然后把对象的指针放进channel 中,  那么目标协程可以从channel中拿到对象指针, 并处理其指向的内存对象。

类似于通信的管道, 虽然有传统共享数据同步机制,但go语言强烈建议我们使用 Channel 通道来实现goroutine 之间的通信

通道就是goroutine 之间的通道, 它可以让goroutine之间相互通信。

通道必须传递自己类型的数据

在这里插入图片描述

刚定义的通道 默认是nil的, 可以通过 make(chan int) 来创建

在这里插入图片描述
上图 证明通道传的是 内存地址

怎么使用 通道:读操作 + 写操作

data := <- a 从通道 a中取出值, 放到 data中

a <- data ; 将数据data放到 a 这个通道中

在这里插入图片描述

package main

import "fmt"

func main(){
	var ch1 chan bool
	ch1 = make(chan bool)
	go func(){
		for i:=0; i<4; i++{
			fmt.Printf("子goroutine 中 i-> %d\n",i)
		}
		// 循环结束后 ,向通道中写入数据
		ch1 <- true// 该操作可以解除读操作的阻塞
		fmt.Println("子goroutine 结束...")
	}()
	
	data := <- ch1//先阻塞, 等子gorooutine写入数据后, 该读操作随机解除阻塞
	fmt.Println("main goroutine read data :",data)
	fmt.Println("main over...")
}

通道 channel 的注意点:

1 用于协程 ,传递消息
2 每个通道都有自己的数据类型 , nil通道,不能直接存储(即先定义,再创建)
3 使用通道传输数据:<-
chan <- data 发送数据到通道,向通道中写数据,
data<-chan 从通道中获取数据
4 阻塞: (必须 有读也有写, 阻塞是同时相互解除的)
发送数据: chan<-data写阻塞的,直到另一个协程读取数据才会解除阻塞,
读取数据: data:= <-chan 也是阻塞的, 直到另一端写出数据解除阻塞
5 本身 通道是同步的, 意味着 同一时间,只能有一条通道来操作。
6 通道是 协程之间的连接, 所以通道的发送和接收必须在不同的协程中。

死锁 : 没有读通道操作, 或者没有写通道操作 ,就会形成死锁

如何 关闭 通道:在子协程中关闭通道

package main

import (
	"fmt"
	"time"
)

func main(){
	/*
		关闭通道
	*/
	ch2 := make(chan int)
	go func(){
		for i:=0; i<10; i++ {
			ch2 <- i
		}
		fmt.Println("子goroutine 结束...")
		close(ch2)
	}()
	for {
		time.Sleep(2 * time.Millisecond)// 保证 数据被写入通道
		data2, ok := <-ch2
		if ok != true {
			fmt.Println("读取通道数据结束...")
			break
		}
		fmt.Println("main goroutine read data ->", data2)
	}
	fmt.Println("main goroutine over...")
}

在这里插入图片描述

在这里插入图片描述

对比发现 ,协程直接 也可能因为时间片原因进行抢占调度执行

缓冲通道

不带缓冲的通道: 只有读/写 会发生阻塞

带缓冲的通道: 数据先发送到缓冲区: 只有缓冲区满了 或者空了才会发生阻塞。

创建缓冲通道 只需要在后边 加上通道大小就可以了

在这里插入图片描述

在这里插入图片描述
在这里插入图片描述
通道都是按照队列的数据结构读取的

定向通道:单向通道

ch1 := make(<- chan int) 只能读不能写的通道
ch2 :=make(chan <- int) 只能写 , 不能读的通道
ch3 : =make(chan int) 双向 不带缓冲通道
ch4 : =make(chan int, 4) 双向 带缓冲通道

你可能会问: 既然 只是单向通道:那么又有什么暖用?

是的: 但向通道往往只是作为参数传递的 ,目的就是为了保护而已。
因为通道本来就是用于两个协程之间的通信的, 设置成单向的,往往用处不大

time包下与 通道相关的函数

主要就是 timer定时器, 可以让用户自己定义超时逻辑, 尤其是在 select 处理多个channel 的超时、单channel读写的超时等情况时尤为方便。

Timer是一次性的时间触发事件。Ticker 是按一定的时间间隔 持续触发事件的。

Timer 的触发方式:
一 : t := timer.NewTimer(d)
t := timer.AfterFunc(d,F)
c:=timer.After(d)
在这里插入图片描述

可以看到 C是一个只写通道,
在这里插入图片描述

package main

import (
	"fmt"
	"time"
)

func main(){
	/*
		1  func NewTimer(d Duration)创建一个计时器, d时间以后触发
	*/
	timer := time.NewTimer(3 * time.Second) //timer是一个time.Timer指针
	fmt.Printf("timer type is %T\n",timer)
	fmt.Println(time.Now())

	//阻塞三秒钟 触发 计时器
	ch2 := timer.C //C 是一个通道
	fmt.Println(<-ch2)// 读取 ch2内的数据 : 读取的是3秒之后的时间
}

在这里插入图片描述

flag :=timer.strop() // 停止定时器

After(d Duration)(chan)  // 作用和NewTimer一样 ,但他返回一个通道, 存储当前时间 + d  的时间。

package main

import (
	"fmt"
	"time"
)

func main(){
	/*
		1  func NewTimer(d Duration)创建一个计时器, d时间以后触发
	*/
	timer := time.NewTimer(3 * time.Second) //timer是一个time.Timer指针
	fmt.Printf("timer type is %T\n",timer)
	fmt.Println(time.Now())

	//阻塞三秒钟 触发 计时器
	ch2 := timer.C //C 是一个通道
	fmt.Println(<-ch2)// 读取 ch2内的数据 : 读取的是3秒之后的时间

	fmt.Println()
	
	ch4 := time.After(3 * time.Second)//After 作用和Newtimer一样,但返回一个 通道
	fmt.Println(time.Now())
	fmt.Println(<-ch4)
}

Select 语句

select 是Go语言提供的一个控制结构(选择语句), 通过select 可以监听在channel上的数据流动

select和switch 语句非常像, 但是 select 会随机执行一个可运行的case, 如果没有case 可运行的话,有 default 会执行default, 没有的话, select将阻塞, 直到有case 可运行

在这里插入图片描述

package main

import "fmt"

func  main(){
	/*
		select和 switch 语句非常相似
	    有多个case 可运行会随机运行一个,
		没有可以运行的 case 会阻塞等待,直到有case 可执行
	*/

	ch1 := make(chan int)
	ch2 := make(chan int)

	go func(){
		ch1 <-10
	}()

	//下面我们进行  从通道ch1 ch2中读
	select {
		case num1 := <-ch1 :
			fmt.Println("ch1 中获取数据->",num1)
			case num2 ,ok := <-ch2:
				if ok{
					fmt.Println("ch2 中读取的数据",num2)
				}else{
					fmt.Println("ch2 通道已经关闭...")
				}
	}
	fmt.Println("main over ...")
}

在这里插入图片描述
在这里插入图片描述
这个时候 select 就进行随机选择case执行了

package main

import (
	"fmt"
	"time"
)

func  main(){
	/*
		select和 switch 语句非常相似
	    有多个case 可运行会随机运行一个,
		没有可以运行的 case 会阻塞等待,直到有case 可执行
	*/

	ch1 := make(chan int)
	ch2 := make(chan int)

	go func(){
		time.Sleep(1* time.Second)
		ch1 <-10
	}()
 	go func(){
 		time.Sleep(1 * time.Second)
 		ch2 <-20
	}()
	//下面我们进行  从通道ch1 ch2中读
	select {
		case num1 := <-ch1 :
			fmt.Println("ch1 中获取数据->",num1)
			case num2 ,ok := <-ch2:
				if ok{
					fmt.Println("ch2 中读取的数据",num2)
				}else{
					fmt.Println("ch2 通道已经关闭...")
				}
	}
	fmt.Println("main over ...")
}

经过测试; select会优先执行 已就绪的语句

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章