四、Golang并发编程:协程间通信与Channel

文章目录

      • channel:
      • 定义channel:
      • channel同步,数据传递:
      • 无缓冲channel: —— 同步通信
      • 有缓冲channel:—— 异步通信
      • 关闭channel:
      • 单向channel:
      • 生产者消费者模型:
      • 定时器:
      • 周期定时:
      • select:
      • select实现fibonacci数列:
      • select 超时处理:

channel:

​ 是一种数据类型。 对应一个“管道”(通道 FIFO)

channel的定义:

	make (chan  在channel中传递的数据类型, 容量)	容量= 0: 无缓冲channel, 容量 > 0 :有缓冲channel

	e.g.makechan int) 或 makechan string , 0)

channel有两个端:
	
	一端:写端(传入端)    chan <-

	另一端: 读端(传出端)<- chan

	要求:读端和写端必须同时满足条件,才在shan上进行数据流动。否则,则阻塞。

var channel = make(chan int)	//创建
channel <- 8	//写入
<- channel		//读出

定义channel:

makechan 类型,容量)

ch :=  makechan string:

写端:	 ch <- "hehe"	。写端写数据,读端不在读。写端阻塞

读端:	str := <- ch	。读端读数据, 同时写端不在写,读端阻塞。

channel同步,数据传递:

写端:	 ch <- "hehe"	。写端写数据,读端不在读。阻塞

读端:	str := <- ch	。读端读数据, 同时写端不在写,读端阻塞。

len(ch) : channel 中剩余未读取数据个数。 cap(ch): 通道的容量。
func main()  {
	ch := make(chan string)		// 无缓冲channel
	// len(ch) : channel 中剩余未读取数据个数。 cap(ch): 通道的容量。
	fmt.Println("len(ch)=", len(ch), "cap(ch)=", cap(ch))
	go func() {
		for i:=0;i<2;i++ {
			fmt.Println("i = ", i,"len(ch)=", len(ch), "cap(ch)=", cap(ch))
		}
		// 通知主go打印完毕
		ch <- "子go打印完毕"
	}()

	str := <- ch
	fmt.Println("str=", str)
}

无缓冲channel: —— 同步通信

创建: ch := make(chan int)make(chan int, 0)

通道容量为0len = 0 。 不能存储数据。

channel 应用于 两个go程中。	一个读,另一个写。  

具备同步的能力。  读、写同步。(打电话)
func main()  {
	ch := make(chan int)

	go func() {
		for i:= 0; i<5;i++ {
			fmt.Println("子go程, i=",i)
			ch <- i			// ch <- 0
		}
	}()
	//time.Sleep(time.Second * 2)
	for i:= 0; i<5;i++ {
		num := <- ch
		fmt.Println("主go程读:", num)
	}
}

有缓冲channel:—— 异步通信

创建: ch := make(chan int, 5)

通道容量为非0len(ch) : channel 中剩余未读取数据个数。 cap(ch): 通道的容量。

channel 应用于 两个go程中。一个读,另一个写。  

缓冲区可以进行数据存储。存储至 容量上限,阻塞。 具备 异步 能力,不需同时操作channel缓冲区(发短信)
func main()  {
	ch := make(chan int, 5)		// 存满3个元素之前,不会阻塞
	fmt.Println("len=", len(ch), "cap=", cap(ch))

	go func() {
		for i:=0; i< 10;i++ {
			ch <- i
			len := len(ch)
			cap := cap(ch)
			fmt.Println("子go程:i", i, "len=", len, "cap=", cap)
			//fmt.Println("子go程:i", i)
		}
	}()
	//time.Sleep(time.Second * 3)
	for i:=0; i< 8;i++ {
		num := <- ch
		fmt.Println("主go程读到:", num)
	}
}

关闭channel:

无、有缓冲
确定不再相对端发送、接收数据。关闭channel。 使用 close(ch) 关闭channel

对端可以判断 channel 是否关闭:

	if num, ok := <-ch ;  ok == true {

	如果对端已经关闭, ok --> false . num无数据。

	如果对端没有关闭, ok --> true . num保存读到的数据。

可以使用 range 替代 ok:

	for num := range ch {		// ch 不能替换为 <-ch
	}

总结:	1. 数据不发送完,不应该关闭。

	  2. 已经关闭的channel, 不能再向其写数据。 报错:panic: send on closed channel

	  3. 写端已经关闭channel, 可以从中读取数据。

				读无缓冲channel: 读到0 。 —— 说明:写端关闭。

				读有缓冲channel: 如果缓冲区内有数据,先读数据。读完数据后,可以继续读。 读到 0
func main()  {
	ch := make(chan int, 0)

	go func() {
		for i:=0; i<5;i++ {
			ch <- i
		}
		close(ch)		// 写端,写完数据主动关闭channel
		//ch <- 790
		fmt.Println("子go 结束")
	}()
	time.Sleep(time.Second * 2)

	for num := range ch {
		fmt.Println("读到数据:", num)
	}
}

单向channel:

默认的channel 是双向的。 var ch chan int		ch = make(chan int)

单向写channel:	var  sendCh chan <-	 int	sendCh = make(chan <- int)	不能读操作

单向读channel:	var  recvCh  <- chan int	recvCh = make(<-chan int)

转换:
	1. 双向channel 可以 隐式转换为 任意一种单向channel 

		sendCh  = ch

	2. 单向 channel 不能转换为 双向 channel

		ch = sendCh/recvCh   error!!!

传参: 传【引用】
func send(out chan <- int)  {
	out <- 89
	close(out)
}

func recv(in <- chan  int)  {
	n := <-in
	fmt.Println("读到", n)
}

func main()  {
	ch := make(chan int) 			// 双向channel
	go func() {
		send(ch)		// 双向channel 转为 写channel
	}()
	recv(ch)
}

生产者消费者模型:

生产者: 发送数据端

消费者: 接收数据端

缓冲区: 	1. 解耦 ( 降低生产者 和 消费者之间 耦合度 )

	       2. 并发 (生产者消费者数量不对等时,能保持正常通信)

	       3. 缓存 (生产者和消费者 数据处理速度不一致时, 暂存数据)

模型代码实现。---- 模拟订单代码实现。 参阅讲义
func producer(out chan<- int)  {
	for i:=0; i<10;i++ {
		fmt.Println("生产:", i*i)
		out <- i*i
	}
	close(out)
}

func consumer(in <- chan int)  {
	for num := range in {
		fmt.Println("消费者拿到:",num)
		time.Sleep(time.Second)
	}
}

func main()  {
	ch := make(chan int, 6)
	go producer(ch)		// 子go程 生产者
	consumer(ch)		// 主go程 消费
}

定时器:

Timer:创建定时器,指定定时时长,定时到达后。 系统会自动向定时器的成员 C 写 系统当前时间。 (对 chan 的写操作)
type Timer struct {
   C <-chan Time
   r runtimeTimer
}

	sleep()
	NewTimer
	After
	
读取 Timer.C 得到 定时后的系统时间。并且完成一次  chan 的 读操作。

time.After() 定时: 	

	指定定时时长,定时到达后。 系统会自动向定时器的成员 写入 系统当前时间。

	返回 可读 chan 。 读取,可获得系统写入时间。

总结: Sleep、NewTimer、After —— time包

定时器的 停止、重置:

	1) 创建定时器 myTimer := time.NewTimer(2 * time.Second)
	
	2)   停止: myTimer.Stop	—— 将定时器归零。    <-myTimer.C 会阻塞

	3) 重置:myTimer.Reset(time.Second)
func main()  {
	fmt.Println("当前时间:", time.Now())
	// 创建定时器
	myTimer := time.NewTimer(time.Second * 2)
	nowTime := <- myTimer.C  // chan 类型
	fmt.Println("现下时间:", nowTime)
}

// 3 种定时方法
func main()  {
	// 1 . sleep
	time.Sleep(time.Second)

	// 2. Timer.C
	myTimer := time.NewTimer(time.Second * 2)		// 创建定时器, 指定定时时长
	nowTime := <- myTimer.C  						// 定时满,系统自动写入系统时间
	fmt.Println("现下时间:", nowTime)

	// 3 time.After
	fmt.Println("当前时间:", time.Now())
	nowTime2 := <-time.After(time.Second * 2)
	fmt.Println("现下时间:", nowTime2)
}

// 定时器的停止和重置
func main()  {

	myTimer := time.NewTimer(time.Second * 10)		// 创建定时器。
	myTimer.Reset(1 * time.Second)		// 重置定时时长为 1
	go func() {
		for {
			<- myTimer.C
			fmt.Println("子go程,定时完毕")
		}
	}()

	//myTimer.Stop()		// 设置定时器停止
	for {
		;
	}
}

周期定时:

type Ticker struct {
	C <-chan Time 
	r runtimeTimer
    }	
1) 创建周期定时器 myTicker := time.NewTicker(time.Second)

	定时时长到达后,系统会自动向 Ticker 的 C 中写入 系统当前时间。 

	并且,每隔一个定时时长后,循环写入 系统当前时间。 

2) 在 子 go 程中循环读取 C。获取系统写入的时间。 
func main()  {

	quit := make(chan bool)		// 创建一个判断是否 终止的channel

	fmt.Println("now:    ", time.Now())
	myTicker := time.NewTicker(time.Second)  // 定义一个周期定时器

	i := 0
	go func() {
		for {
			nowTime := <-myTicker.C
			i++
			fmt.Println("nowTime:", nowTime)
			if i == 3 {
				quit <- true	// 解除 主go程阻塞。
				break // return // runtime.Goexit
			}
		}
	}()

	<-quit		// 子go程 循环获取 <-myTicker.C 期间,一直阻塞
}

select:

​ 作用: 用来监听 channel 上的数据流动方向。 读?写?

用法: 参考 switch case 语句。 但!case后面必须是IO操作,不可以任意写判别表达式。

注意事项:
	1. 监听的case中,没有满足监听条件,阻塞。

	2. 监听的case中,有多个满足监听条件,任选一个执行。

	3. 可以使用default来处理所有case都不满足监听条件的状况。 通常不用(会产生忙轮询)

	4. select 自身不带有循环机制,需借助外层 for 来循环监听

	5. break 跳出 select中的一个case选项 。类似于switch中的用法。
func main()  {
	ch := make(chan int)			// 用来进行数据通信的 channel
	quit := make(chan bool)			// 用来判断是否退出的 channel
	//ch2 := make(chan string)
	go func() {					// 写数据
		for i:=0; i<5;i++{
			ch <- i
			time.Sleep(time.Second)
		}
		close(ch)
		quit <- true			// 通知主go程 退出
		runtime.Goexit()
	}()
	for {						// 主go程 select 监听 chan数据流动
		select {
		case num := <-ch:		// 不可读,阻塞。可以读,将数据保存至num
			fmt.Println("读到:", num)		// 模拟使用数据

		case <-quit:			// 不可读,阻塞。可以读,将主go程结束。
			break 				// break 跳出 select	不可用
			//runtime.Goexit()	// 终止 主 go 程		不可用
			//return 				// 终止进程
		}
		fmt.Println("============")		// select 自身不带有循环机制,需借助外层 for 来循环监听
	}
}

select实现fibonacci数列:

代码思路:
1  1  2  3  5  8  13  21  34  55   89 
x = y
y = x+y	

代码:

func fibonacci(ch <-chan int, quit <-chan bool)  {
	for {
		select {
		case num := <-ch :
			fmt.Print(num , " ")
		case <-quit:
			//return
			runtime.Goexit()		//等效于 return
		}
	}
}

func main()  {
	ch := make(chan int)
	quit := make(chan bool)

	go fibonacci(ch, quit)		// 子go 程 打印fibonacci数列

	x, y := 1, 1
	for i:=0; i<40;i++ {
		ch <- x
		x, y = y, x+y
	}
	quit <- true
}

select 超时处理:

select 监听 time.After() 中 channel 的读事件。  如果定时时间到,系统会向该channel中写入系统当前时间。

select {
        case  <-time.After(time.Second * 5)
	定时到达后,要处理的内容
} 
//以下有个goto的使用
func main()  {
	ch := make(chan int)
	quit := make(chan bool)
	go func() {			// 子go 程获取数据
		for {
			select {
			case num := <-ch:
				fmt.Println("num = ", num)
			case <-time.After(3 * time.Second):
				quit <- true
				goto lable
				// return
				// runtime.Goexit()
			}
		}
	lable:
	}()
	for i:=0; i<2; i++ {
		ch <- i
		time.Sleep(time.Second * 2)
	}
	<-quit 			// 主go程,阻塞等待 子go程通知,退出
	fmt.Println("finish!!!")
}

你可能感兴趣的:(golang,go语言,并发编程,编程语言,多线程)