Day11 Channel

通道Channel

Channel可以想像成是一种资料结构,可以push data进去也可以pull data出来。

因为Channel会等待另一端完成Push/Pull的动作才会继续往下处理,而且特性使其可以在Goroutines间同步处理的资料,而不用使用lock, unlock等方式。

Create Channel

这里示范建立一个int型别的channel

ch := make(chan int)

Push data to Channel

将data d 送入channel ch之中

ch <- d 

Pull data from Channel

将data从channel ch拿出,并赋予给变数d

d := <- ch

Channel是用来让Goroutine沟通时使用的一种资料结构,并且由於其阻塞的特性,它也能够当成一种等待goroutine的方法。

Goroutine with Channel

package main

import (
	"fmt"
	"time"
)

func main() {
	ch := make(chan string)

	go calculator(ch)

	time.Sleep(3 * time.Second)
	fmt.Println(<-ch)
	time.Sleep(time.Second)
	fmt.Println("main goroutine finished")
}

func calculator(ch chan string) {
	fmt.Println("Start to calculate goroutines")
	time.Sleep(time.Second)
	fmt.Println("Stop to calculate goroutines")

	ch <- "FINISH"

	fmt.Println("Finish calculator")
}

运行後可得以下结果

Start to calculate goroutines
Stop to calculate goroutines
FINISH
Finish calculator
Main goroutine finished

那我们这边的三秒延迟目的是为了让main thread慢於goroutines,一秒延迟则是模拟goroutines的作业时间! 所以他会依序的去进行goroutines作业→打印channel的内容→完成goroutines function→完成main thread。

通道分为两种,有buffer与无buffer的,也就是有储存空间限制的channel与无限制的channel。

此外,通道也能分为单向与双向两种,就是能传资料的callervs只有一方能传资料的callee

Unbuffered channel

Variable := make(chan Type)

chan 被制造出来之後,需要传入 要被你并发出去的func,它靠这种方式传资料。

chan是有方向性的,要看箭头←的方向。

chan <- A `把 A这个东西 塞进chan`
B<- chan  `从chan 挖东西出来 到B`

由下两范例来说明接收的方向性:

package main

import (
        "fmt"
)

func main() {
	ch := make(chan int)
	go func1(ch)
	ch <- 100
}

func func1(ch chan int) {
	i := <-ch
	fmt.Println(i)
}
package main

import (
        "fmt"
	"time"
)

func main() {
	ch := make(chan int)
	go func2(ch)
	got := <-ch
	fmt.Println(got)
}

func func2(ch chan int) {
	time.Sleep(time.Second * 2)
	ch <- 999
}

运行後可得以下结果

999

由上面例子我们可以得知,Unbuffered Channel有一个特性:

  • Push一笔资料会造成Pusher的等待
  • Pull时没有资料则会造成Puller的等待

也因此如果Pusher的执行一次时间较Puller短,会造成Pusher被迫等待Puller拉取才能进行下一次的push,而这样的等待是相当浪费时间的。

为了解决此问题,因此诞生了另一种Channel,Buffered Channel。

Buffered channel

Variable := make(chan Type, Number)

有限制储存空间的通道,若限制放两个,就只能有两笔数据,倘若塞入第三笔的话则会造成死结DeadLock

Deadlock

package main

func main() {
	ch := make(chan int, 2)
	go func3(ch)
	ch <- 100
	ch <- 99

	ch <- 98 // 发生deadlock
}

func func3(ch chan int) {
}

运行後可得以下结果

fatal error: all goroutines are asleep - deadlock!

goroutine 1 [chan send]:
main.main()
	/tmp/sandbox241095799/prog.go:9 +0xa7

Block vs Deadlock

以上例来说,通常chan塞不下第三笔数据时,只会发生Block(阻塞滞留),而当Block永远无法解开的情况发生,则是 Deadlock(死结)。上面会发生死结是因为 不论等多久,都不会从Block的状态中脱离。

只要通道(Chan)塞不下,或者没东西可挖,都会发生Block阻塞。

Block

以下是通道Channel 阻塞Block的例子

package main

import (
        "fmt"
	"time"
)

func main() {
	ch := make(chan int, 2)
	go func1(ch)
	for i := 0; i < 10; i++ {
		ch <- i
		fmt.Println("main sent", i)
	}
	time.Sleep(time.Second)
}

func func1(ch chan int) {
	for {
		i := <-ch
		fmt.Println("func1 got", i)
		time.Sleep(time.Millisecond * 100)
	}
}

运行後可得以下结果

main sent 0
main sent 1
main sent 2
func1 got 0
func1 got 1
main sent 3
func1 got 2
main sent 4
func1 got 3
main sent 5
func1 got 4
main sent 6
func1 got 5
main sent 7
func1 got 6
main sent 8
func1 got 7
main sent 9
func1 got 8
func1 got 9

主程序不间断地连续塞十次数字送完休息1秒;而func1每0.1秒吃下来一个数值。

虽然慢,但程序不会打死结,只是会造成执行效率低下而已。

如果把Buffer Size: 2换成5

package main

import (
        "fmt"
	"time"
)

func main() {
	ch := make(chan int, 2)
	go func1(ch)
	for i := 0; i < 10; i++ {
		ch <- i
		fmt.Println("main sent", i)
	}
	time.Sleep(time.Second)
}

func func1(ch chan int) {
	for {
		i := <-ch
		fmt.Println("func1 got", i)
		time.Sleep(time.Millisecond * 100)
	}
}

运行後可得以下结果

main sent 0
main sent 1
main sent 2
main sent 3
main sent 4
main sent 5
func1 got 0
func1 got 1
main sent 6
func1 got 2
main sent 7
func1 got 3
main sent 8
func1 got 4
main sent 9
func1 got 5
func1 got 6
func1 got 7
func1 got 8
func1 got 9

同时间通道里最多会有五个数字。

塞与取的先後顺序,透过log.SetFlags(5)来看会比较清楚。

无缓冲通道不等於无限通道

The buffer size is the number of elements that can be sent to the channel without the send blocking.

Buffer 是拿来缓冲用的,Unbuffered Channel则是0缓冲

,就是没有缓冲啦!Unbuffered 是需要有同时有一头写入、另一头读出,才能动的。

那Golang有没有 无限制的通道(Unlimited)呢?

当然是 没有!

因为倘若需要给个100000byte大小的Channel必须先挪出100000byte的记忆体空间出来,如此一来多创几个Unlimited Chaneel就会造成Out Of Memory了!

Summary

这章节我们讲述了Channel的用法,Unbuffered与Buffered的差别,彼此所遇到的问题与困难,以及它们各自搭配goroutines的使用范例,


<<:  DAY14: HTPP服务器:Respone对象

>>:  DAY11-EXCEL统计分析:卡方检定介绍

故事二十九:今晚,简单练习就好!

     再过一天,比赛就结束了。时间真的过得好快啊!   今晚我从 open data 网站,下载...

AutoCAD ActiveX #4 Block & Layer

Layer A logical grouping of data, similar to trans...

Kneron - Kneron Toolchain 转档操作参考笔记

Kneron - Kneron Toolchain 转档操作参考笔记 参考资料 onnx 档案来源:...

资料存取的先後顺序:Stack 和 Queue

题组回顾与观念统整 Stack 和 Queue 绝对是资料结构中不可以错过的一种容器,不只用於资料...

[进阶指南] Portal( Day26 )

Portal 提供一个优秀方法来让 children 可以 render 到 parent com...