Young87

当前位置:首页 >个人收藏

并发技术3:管道通信

#channel 介绍
channel 提供了一种通信机制,通过它,一个 goroutine 可以想另一 goroutine 发送消息。channel 本身还需关联了一个类型,也就是 channel 可以发送数据的类型。例如: 发送 int 类型消息的 channel 写作 chan int 。
#channel 创建
channel 使用内置的 make 函数创建,下面声明了一个 chan int 类型的 channel:

ch := make(chan int)

c和 map 类似,make 创建了一个底层数据结构的引用,当赋值或参数传递时,只是拷贝了一个 channel 引用,指向相同的 channel 对象。和其他引用类型一样,channel 的空值为 nil 。使用 == 可以对类型相同的 channel 进行比较,只有指向相同对象或同为 nil 时,才返回 true
#channel 的读写操作

ch := make(chan int)

// write to channel
ch <- 123

// read from channel
x := <- ch

// another way to read
x = <- chnnel 一定要初始化后才能进行读写操作,否则会永久阻塞。

channel 一定要初始化后才能进行读写操作,否则会永久阻塞。

#关闭 channel
golang 提供了内置的 close 函数对 channel 进行关闭操作。

ch := make(chan int)
close(ch)

有关 channel 的关闭,你需要注意以下事项:

  1. 关闭一个未初始化(nil) 的 channel 会产生 panic
  2. 重复关闭同一个 channel 会产生 panic
  3. 向一个已关闭的 channel 中发送消息会产生 panic
  4. 从已关闭的 channel 读取消息不会产生 panic,且能读出 channel中还未被读取的消息,若消息均已读出,则会读到类型的零值。从一个已关闭的 channel 中读取消息永远不会阻塞,并且会返回一个为
  5. false 的 ok-idiom,可以用它来判断 channel 是否关闭
  6. 关闭 channel 会产生一个广播机制,所有向 channel 读取消息的 goroutine 都会收到消息
ch := make(chan int, 10)
ch <- 11
ch <- 12

close(ch)

for x := range ch {
    fmt.Println(x)
}

x, ok := <- ch
fmt.Println(x, ok)


-----
output:

11
12
0 false

#channel 的类型
channel 分为不带缓存的 channel 和带缓存的 channel。

#无缓存的 channel
从无缓存的 channel 中读取消息会阻塞,直到有 goroutine 向该 channel 中发送消息;同理,向无缓存的 channel 中发送消息也会阻塞,直到有 goroutine 从 channel 中读取消息。

通过无缓存的 channel 进行通信时,接收者收到数据 happens before 发送者 goroutine 唤醒

#有缓存的 channel
有缓存的 channel 的声明方式为指定 make 函数的第二个参数,该参数为 channel 缓存的容量

ch := make(chan int, 10)

有缓存的 channel 类似一个阻塞队列(采用环形数组实现)。当缓存未满时,向 channel 中发送消息时不会阻塞,当缓存满时,发送操作将被阻塞,直到有其他 goroutine 从中读取消息;相应的,当 channel 中消息不为空时,读取消息不会出现阻塞,当 channel 为空时,读取操作会造成阻塞,直到有 goroutine 向 channel 中写入消息。

ch := make(chan int, 3)

// blocked, read from empty buffered channel
<- ch

上面的例子,没人写,读取一直被阻塞

ch := make(chan int, 3)
ch <- 1
ch <- 2
ch <- 3

// blocked, send to full buffered channel
ch <- 4

上面的例子,写入3个数据,耗尽缓存能力,再写就阻塞了

通过 len 函数可以获得 chan 中的元素个数,通过 cap 函数可以得到 channel 的缓存长度。

#实例
###通过channel实现同步

####导入依赖

import (
	"fmt"
	"time"
)
//语法点①:创建int类型的无缓存管道
//var ch = make(chan int)
var ch = make(chan int,0)

func Printer(str string) {
	for _, data := range str {
		fmt.Printf("%c", data)
		time.Sleep(time.Second)
	}
	fmt.Printf("\n")
}

func person1() {
	//打印完需要7秒钟
	//劳资不打印完是不会往管道中塞数据的,阻塞不死你丫的
	Printer("今生注定我爱你")

	//箭头指向管道内部,写数据
	//在打完今生注定我爱你(耗时7秒钟)后,才写入数据
	//语法点②:向管道里写数据,无论读写,箭头只能朝左
	//语法点⑤:如果管道缓存已满,则阻塞等待至有人取出数据腾出空间,再写入
	ch <- 666
}

func person2() {
	//箭头指向管道外面,代表从管道中拿出数据,读数据

	//语法点③:从管理取出数据,但不不接收
	//语法点⑥:管道里没数据时,阻塞死等
	<-ch

	//语法点④:从管理取出数据,且使用data变量接收
	//data:=<-ch
	//fmt.Println("读出数据:",data)

	//终于妈的可以打印了
	Printer("FUCKOFF")
}

func main() {

	go person1()
	go person2()

	//主协程赖着不死
	for {
		time.Sleep(time.Second)
	}
}

###通过channel实现同步和数据交互

package main

import (
	"fmt"
	"time"
)

func main() {
	//创建无缓存管道
	ch := make(chan string)

	//5、主协程结束
	defer fmt.Println("主协程也结束")

	//子协程负责写数据
	go func() {
		//3、结束任务
		defer fmt.Println("子协程调用完毕")

		//1、缓缓打印2次序号
		for i := 0; i < 2; i++ {
			fmt.Println("子协程 i= ", i)
			time.Sleep(time.Second)
		}

		//2、向管道发送数据
		ch <- "我是子协程,工作完毕"
	}()

	//4、阻塞接收
	str := <-ch
	fmt.Println("str = ", str)
}

###无缓冲的channel

package main

import (
	"fmt"
	"time"
)

func main() {
	//创建一个无缓冲的管道
	ch := make(chan int, 1)

	//长度0,缓存能力0
	fmt.Printf("len(ch) = %d, cap(ch)=%d\n", len(ch), cap(ch))

	go func() {
		//向管道中存入0,被阻塞,存入1,被阻塞,存入2
		for i := 0; i < 3; i++ {
			fmt.Println("子协程: i = ", i)
			ch <- i

			fmt.Println("5秒以内被打印出来给杰神100万!")
		}
	}()

	//睡眠2秒
	time.Sleep(5 * time.Second)

	//读取0,被阻塞,读取1,被阻塞,读取2
	for i := 0; i < 3; i++ {
		num := <-ch
		fmt.Println("num = ", num)
	}

}

###有缓存的channel

package main

import (
	"fmt"
	"time"
)

func main() {
	//创建3缓存的管道
	ch := make(chan int, 3)
	//长度0,缓存能力3(即使没人读,也能写入3个值)
	fmt.Printf("len(ch) = %d, cap(ch) = %d\n", len(ch), cap(ch))

	//一次性存入3个:012,3456789
	go func() {
		for i := 0; i < 10; i++ {
			ch <- i
			fmt.Printf("子协程存入[%d]: len(ch) = %d, cap(ch) = %d\n", i, len(ch), cap(ch))
			//time.Sleep(1 * time.Second)
		}
	}()

	//time.Sleep(5 * time.Second)

	//一次性读取3个:012,345,678,9
	for i := 0; i < 10; i++ {
		num := <-ch
		fmt.Println("num = ", num)
	}
	time.Sleep(1*time.Nanosecond)
}

学院Go语言视频主页
https://edu.csdn.net/lecturer/1928

[清华团队带你实战区块链开发]

(https://ke.qq.com/course/344443?tuin=3d17195d)
扫码获取海量视频及源码 QQ群:721929980
在这里插入图片描述

除特别声明,本站所有文章均为原创,如需转载请以超级链接形式注明出处:SmartCat's Blog

上一篇: 图形用户界面2:常用控件

下一篇: 并发技术5:死锁问题

精华推荐