草庐IT

《Go 语言并发之道》读书笔记(五)

dk168 2023-03-28 原文

今天这篇笔记我们来记录Channel 和 Select, Go语言并发中Channel是goroutine传递数据的桥梁,是非常重要的一个工具。

定义Channel

双向Channel

要定义一个channel很简单,只需要在类型前面加上chan就可以了,
stringStream := make(chan string)
这样就是定义和实例化了一个string 类型的双向channel,
先来看一个Hello World的例子

func main() {

	stringStream := make(chan string)
	go func() {
		stringStream <- "Hello channels"
	}()

	fmt.Println(<-stringStream)

运行代码控制台打印出“Hello channels”, 这个简单的例子中我们定义了一个string类型的channel, 启动一个goroutine, 往这个channel中写入“Hello channels”, 主的goroutinue会读取这个channel里面的value, 读取是阻塞的,如果我们把写的代码注释掉“stringStream <- "Hello channels"”,程序运行会报死锁,因为没有谁会写入了,它一直等待。

单向Channel

我们也可以声明单向的channel,也就是只读或者只写的channel.
var receiveChan <-chan string //一个只读的channel
var sendChan chan<- string //一个只写的channel,
既然是只读,那么谁来给它写入呢, 这里其实还是需要一个双向的channel,然后把双向的channel赋值给单向channel,如
stringStream := make(chan string)
receiveChan = stringStream
sendChan = stringStream

只读和只写channel有什么作用呢? 他们主要是用在方法的参数或者返回中,用户看到这个chan是只读的或者只写的就明确了它的使用方法。 对于只读的,我们实际上用个双向的channel,然后写入双向channel后, 把双向channel赋值给只读的channel. 如下示例代码

func main() {
	stringStream := make(chan string)
	go send(stringStream, "passed message")
	receive(stringStream)
}

func send(pings chan<- string, msg string) {
	fmt.Println("ping " + msg)
	pings <- msg
}

func receive(receiver <-chan string) {
	fmt.Println(<-receiver)
}

我们在send方法中知道pings是只写的,不会读取它
在receive方法中知道receiver是只读的, 不会写它

读取和写入Channel

上面例子我们一件看到读就通过value := <-channel, 把channel中的数据读出来, 写就通过 channel<- value, 箭头方向也比较明确,比较好理解。这里再给个通过range读取channel的方法
先看示例代码

	intStream := make(chan int)

	go func() {
		defer close(intStream)
		for i := 1; i <= 5; i++ {
			intStream <- i
			fmt.Printf("writer %d \n", i)
		}
	}()

	for integer := range intStream {
		fmt.Printf("receive %v \n", integer)
	}

	fmt.Println(<-intStream)
	fmt.Println(<-intStream)

我们写入了五个value到intStream里面, 读取的时候通过range我就不用知道这个次数了,通过for range 就都拿到了。 上面程序输出结果如下:

writer 1 
receive 1 
receive 2
writer 2
writer 3
receive 3
receive 4
writer 4
writer 5
receive 5
0
0

结果比较有意思, receive 2 跑到writer3的前面去了, 我猜测是这个channel是阻塞的,写入的时候,必须读了才能再写,读到1以后,2就可以写了,还没有来得及打印writer, read就拿到了。所以感觉上receive跑到writer前面去了。
最后两个00是我故意打印出来的,从关闭的channel也能拿到有返回的数据,如果想确定数据是不是正常写入的,可以加上 value,ok := <- intStream, 判断 ok 是true和false判断是否是正常写入的。

缓冲Channel

我们前面看到的例子,写入数据到channel后,必须等别的goroutine读到后才可以继续写,那么如果我想写入后继续去干别的,就需要用到缓冲Channel, 也就可以多写几个到channel。 如下示例代码

	intStream := make(chan int, 2)
	go func() {
		defer close(intStream)
		defer fmt.Println("Producer Done")
		for i := 0; i < 5; i++ {
			intStream <- i
			fmt.Printf("Sending: %d \n", i)

		}
	}()

	time.Sleep(10 * time.Second)

	for i := 0; i < 5; i++ {
		v := <-intStream
		fmt.Printf("Received: %d \n", v)
		time.Sleep(1 * time.Second)
	}

定义一个缓冲区为2的channel, 当写入两个后会被阻塞。 输出结果如下

Sending: 0 
Sending: 1 
Received: 0 
Sending: 2
Received: 1 
Sending: 3 
Received: 2 
Sending: 4 
Producer Done
Received: 3 
Received: 4 

可以看到当发送了两个后,发送就阻塞起来了,直到读取了之后,才可以继续发送。
这里有个疑问点作者说 make(chan int) 和 make(chan int, 0) 是等效的,我实际验证效果也确实是一样的,但是我想不应该是make(chan int, 1) 吗?但是实际效果make(chan int, 1) 和make(chan int, 0) 确实不一样。 我实验了下,make(chan int, 1) 写第二个的时候被阻塞,用
make(chan int, 0),写一开始就会阻塞,直到开始读了,写才会成功。当然不是先写后读,只是一种相互的阻塞状态。

Select

作者在书中写道:“Select是一个具有并发性的Go语言最重要的事情之一, 在一个系统中两个或者多个组件的交集中,可以在本地、单个函数、或者类型以及全局范围内查找select语句绑定在一起的channel。除了连接组件之外,在程序的某些关键节点上, select 语句可以帮助安全地将channel与诸如取消、超时、等待、默认值之类的概率结合起来”。

单一channel select

先来看一个简单的例子

	start := time.Now()
	c := make(chan interface{})
	go func() {
		time.Sleep(5 * time.Second)
		close(c)
	}()

	fmt.Print("Blocking on read ... \n")

	select {
	case <-c:
		fmt.Printf("Unblocked %v later. \n", time.Since(start))
	}

程序输出结果如下, 等待5S后,关闭了channel, 阻塞结束。

Blocking on read ... 
Unblocked 5.0101794s later. 

上面是一个单一channel select的例子, 它等效于下面的语句

if c == nil {
    block()
}
<- c

多个channel

接着我们看一个多个channel可用的例子, 我自己稍微改装了一下上面的例子

	start := time.Now()
	c := make(chan interface{})
	c2 := make(chan int)
	go func() {
		time.Sleep(5 * time.Second)
		for i := 0; i < 3; i++ {
			c2 <- i
		}

		close(c)
	}()

	fmt.Print("Blocking on read ... \n")

loop:
	for {
		select {
		case <-c:
			fmt.Printf("Unblocked %v later. \n", time.Since(start))
			break loop
		case data := <-c2:
			fmt.Printf("C2 received %d,  %v later. \n", data, time.Since(start))
		}
	}

这里有两个case, 一个收到后会退出循环,一个会读取channel里面的数据,程序运行结果如下所示

Blocking on read ... 
C2 received 0,  5.0148217s later. 
C2 received 1,  5.0155568s later. 
C2 received 2,  5.0161642s later.
Unblocked 5.0167839s later.

我们写入的3个数据都被读取到了,并且关闭channel后退出了循环。

书中还列举了一个当多个channel都可用的时候,Go 语言执行伪随机选择,

	c1 := make(chan interface{})
	close(c1)
	c2 := make(chan interface{})
	close(c2)

	var c1Count, c2Count int
	for i := 1000; i >= 0; i-- {
		select {
		case <-c1:
			c1Count++
		case <-c2:
			c2Count++
		}
	}

	fmt.Printf("c1Count:%d \nc2Count: %d \n", c1Count, c2Count)

程序运行结果如下

c1Count:483 
c2Count: 518

运行1000次,两个case比较平均的执行

超时

我们来看一个超时的例子

	start := time.Now()
	c1 := make(chan interface{})
	select {
	case <-c1:
		fmt.Println("received c1.")
	case <-time.After(2 * time.Second):
		fmt.Printf("Timed out. after %v later. \n", time.Since(start))
	}

程序输出
Timed out. after 2.0099758s later.
没有程序写入c1, 所以在等待2S后,执行了time out.

default

来看default的例子

	start := time.Now()
	var c1, c2 <-chan interface{}
	select {
	case <-c1:
		fmt.Println("received c1.")
	case <-c2:
		fmt.Println("received c2.")
	default:
		fmt.Printf("default after %v later. \n", time.Since(start))
	}

程序几乎立刻执行了default, 输出如下
default after 0s later.

有关《Go 语言并发之道》读书笔记(五)的更多相关文章

  1. ruby - 如何将脚本文件的末尾读取为数据文件(Perl 或任何其他语言) - 2

    我正在寻找执行以下操作的正确语法(在Perl、Shell或Ruby中):#variabletoaccessthedatalinesappendedasafileEND_OF_SCRIPT_MARKERrawdatastartshereanditcontinues. 最佳答案 Perl用__DATA__做这个:#!/usr/bin/perlusestrict;usewarnings;while(){print;}__DATA__Texttoprintgoeshere 关于ruby-如何将脚

  2. ruby - 寻找通过阅读代码确定编程语言的ruby gem? - 2

    几个月前,我读了一篇关于ruby​​gem的博客文章,它可以通过阅读代码本身来确定编程语言。对于我的生活,我不记得博客或gem的名称。谷歌搜索“ruby编程语言猜测”及其变体也无济于事。有人碰巧知道相关gem的名称吗? 最佳答案 是这个吗:http://github.com/chrislo/sourceclassifier/tree/master 关于ruby-寻找通过阅读代码确定编程语言的rubygem?,我们在StackOverflow上找到一个类似的问题:

  3. Unity 热更新技术 | (三) Lua语言基本介绍及下载安装 - 2

    ?博客主页:https://xiaoy.blog.csdn.net?本文由呆呆敲代码的小Y原创,首发于CSDN??学习专栏推荐:Unity系统学习专栏?游戏制作专栏推荐:游戏制作?Unity实战100例专栏推荐:Unity实战100例教程?欢迎点赞?收藏⭐留言?如有错误敬请指正!?未来很长,值得我们全力奔赴更美好的生活✨------------------❤️分割线❤️-------------------------

  4. 7个大一C语言必学的程序 / C语言经典代码大全 - 2

    嗨~大家好,这里是可莉!今天给大家带来的是7个C语言的经典基础代码~那一起往下看下去把【程序一】打印100到200之间的素数#includeintmain(){ inti; for(i=100;i 【程序二】输出乘法口诀表#includeintmain(){inti;for(i=1;i 【程序三】判断1000年---2000年之间的闰年#includeintmain(){intyear;for(year=1000;year 【程序四】给定两个整形变量的值,将两个值的内容进行交换。这里提供两种方法来进行交换,第一种为创建临时变量来进行交换,第二种是不创建临时变量而直接进行交换。1.创建临时变量来

  5. LC滤波器设计学习笔记(一)滤波电路入门 - 2

    目录前言滤波电路科普主要分类实际情况单位的概念常用评价参数函数型滤波器简单分析滤波电路构成低通滤波器RC低通滤波器RL低通滤波器高通滤波器RC高通滤波器RL高通滤波器部分摘自《LC滤波器设计与制作》,侵权删。前言最近需要学习放大电路和滤波电路,但是由于只在之前做音乐频谱分析仪的时候简单了解过一点点运放,所以也是相当从零开始学习了。滤波电路科普主要分类滤波器:主要是从不同频率的成分中提取出特定频率的信号。有源滤波器:由RC元件与运算放大器组成的滤波器。可滤除某一次或多次谐波,最普通易于采用的无源滤波器结构是将电感与电容串联,可对主要次谐波(3、5、7)构成低阻抗旁路。无源滤波器:无源滤波器,又称

  6. ruby-on-rails - 获取并发布相同匹配项的请求 - 2

    在我的路线文件中我有:match'graphs/(:id(/:action))'=>'graphs#(:action)'如果是GET请求(工作)或POST请求(不工作),我想匹配它我知道我可以使用以下方法在资源中声明POST请求:post'/'=>:show,:on=>:member但是我怎样才能为比赛做到这一点呢?谢谢。 最佳答案 如果你同时想要POST和GETmatch'graphs/(:id(/:action))'=>'graphs#(:action)',:via=>[:get,:post]编辑默认值可以设置如下match'g

  7. ruby - 如何保持我不常用的编程语言技能 - 2

    关闭。这个问题是off-topic.它目前不接受答案。想改进这个问题吗?Updatethequestion所以它是on-topic用于堆栈溢出。关闭11年前。Improvethisquestion我不经常使用ruby​​-通常它加起来相当于每两个月或更长时间编写一次脚本。我的大部分编程都是使用C++进行的,这与ruby​​有很大不同。由于我与ruby​​之间的差距如此之大,我总是忘记语言的基本方面(比如解析文本文件和其他简单的东西)。我想每天练习一些基本的东西,我想知道是否有一些我可以订阅的网站,并且会向我发送当天的Ruby问题或类似的东西。有人知道这样的站点/Internet服务吗?

  8. ruby-on-rails - 如果特定语言环境中缺少翻译,如何配置 i18n 以使用 en 语言环境? - 2

    如果特定语言环境中缺少翻译,如何配置i18n以使用en语言环境翻译?当前已插入翻译缺失消息。我正在使用RoR3.1。 最佳答案 找到相似的question这里是答案:#application.rb#railswillfallbacktoconfig.i18n.default_localetranslationconfig.i18n.fallbacks=true#railswillfallbacktoen,nomatterwhatissetasconfig.i18n.default_localeconfig.i18n.fallback

  9. ruby-on-rails - 如何通过 URL 更改语言环境? - 2

    在我的双语Rails4应用程序中,我有一个像这样的LocalesController:classLocalesController用户可以通过此表单更改其语言环境:deflocale_switcherform_tagurl_for(:controller=>'locales',:action=>'change_locale'),:method=>'get',:id=>'locale_switcher'doselect_tag'set_locale',options_for_select(LANGUAGES,I18n.locale.to_s)end这有效。但是,目前用户无法通过URL更改

  10. ruby - 一种语言如何被自身解释(如 Rubinius)? - 2

    我使用Ruby编程已经有一段时间了,现在只使用Ruby的标准MRI实现,但我一直对我经常听到的其他实现感到好奇。前几天我在读有关Rubinius的文章,这是一个用Ruby编写的Ruby解释器。我试着在不同的地方查找它,但我很难弄清楚这样的东西到底是如何工作的。我在编译器或语言编写方面从来没有太多经验,但我真的很想弄明白。一门语言究竟如何才能被自己解释?编译中是否有一个我不明白这有意义的基本步骤?有人可以像我是个白痴一样向我解释这个吗(因为无论如何这都不会太离谱) 最佳答案 它比你想象的要简单。Rubinius并非100%用Ruby编

随机推荐