内容简介:Go语言的select语句语法如下:在select语句中Go语言会按顺序评估每一个发送和接收语句,如果任何语句可以继续进行(即没有被阻塞),则任意选择这些语句中一个来执行;如果没有可以继续执行的语句(即全部被阻塞),这可以分两种情况讨论,如果存在default语句块,则执行该default块中的语句且会从紧接着select语句的后面恢复执行,如果不存在default语句块,select语句将会阻塞直到至少有一个通信可以继续执行。select语句的逻辑结果如下。在没有default语句块的情况下,select
5.4.1. Select语句
Go语言的select语句语法如下:
select { case sendOrReceive1: block1 ... case sendOrReceiveN: blockN default: blockD }
在select语句中 Go 语言会按顺序评估每一个发送和接收语句,如果任何语句可以继续进行(即没有被阻塞),则任意选择这些语句中一个来执行;如果没有可以继续执行的语句(即全部被阻塞),这可以分两种情况讨论,如果存在default语句块,则执行该default块中的语句且会从紧接着select语句的后面恢复执行,如果不存在default语句块,select语句将会阻塞直到至少有一个通信可以继续执行。
select语句的逻辑结果如下。在没有default语句块的情况下,select会被阻塞,且会在一个通信(接收或发送数据)发生时才会执行。如果存在default语句块,则select不会被阻塞且立即实行,这是因为可能有的case语句块有通信发生,或直接执行default语句块。
为了掌握这些语法,让我们来展示两个简短的例子。第一个例子是我们故意设计如此,但是却很好的说明了select语句的工作原理。第二个例子则更贴合实际使用。
channels := make([]chan bool, 6) for i := range channels { channels[i] = make(chan bool) } go func() { for { channels[rand.Intn(6)] <- true }
在上面的代码片段中,我们创建了六个可以发送和接收布尔值的channel。然后,我们创建了一个具有无限循环的goroutine,在这个循环中,每次迭代都可以随机选择一个channel并发送一个true值。当然,该goroutine会立即阻塞,因为这些channel是没有缓冲的且我们还没有从它们中接收数据。
for i := 0; i < 36; i++ { var x int select { case <-channels[0]: x = 1 case <-channels[1]: x = 2 case <-channels[2]: x = 3 case <-channels[3]: x = 4 case <-channels[4]: x = 5 case <-channels[5]: x = 6 } fmt.Printf("%d ", x) } fmt.Println() 6 4 6 5 4 1 2 1 2 1 5 5 4 6 2 3 6 5 1 5 4 4 3 2 3 3 3 5 3 6 5 2 2 3 6 2
上面的代码片段,我们使用了六个channel来模拟一个骰子(严格地说,这是伪随机的)。 select语句会等待其中一个channel发送数据,这种情况下select语句是被阻塞的,因为没有default语句块,但只要有一个或多个channel准备好发送数据,其中一个case语句块就会被随机选择并执行。因为select语句在一个普通的循环中,其执行次数是有限制的。
现在让我们来看一个更贴合实际的例子。假设我们想要在两个单独的数据集上执行大量的计算任务,该计算会产生一系列结果。下面是执行此类计算的函数。
func expensiveComputation(data Data, answer chan int, done chan bool) { // setup ... finished := false for !finished { // computation ... answer <- result } done <- true }
该函数接收要处理的数据和两个channel作为参数。answer channel用于将每个结果发送到监视代码,done channel用于通知监视代码计算已完成。
// setup ... const allDone = 2 doneCount := 0 answerα := make(chan int) answerβ := make(chan int) defer func() { close(answerα) close(answerβ) }() done := make(chan bool) defer func() { close(done) }() go expensiveComputation(data1, answerα, done) go expensiveComputation(data2, answerβ, done) for doneCount != allDone { var which, result int select { case result = <-answerα: which = 'α' case result = <-answerβ: which = 'β' case <-done: doneCount++ } if which != 0 { fmt.Printf("%c→%d ", which, result) } } fmt.Println() α→3 β→3 α→0 β→9 α→0 β→2 α→9 β→3 α→6 β→1 α→0 β→8 α→8 β→5 α→0 β→0 α→3
上面的代码创建了channel,开始耗时的计算,监视进度,并进行最后的清理工作,它们之间完全互不影响。
我们首先创建了answerα 和answerβ这两个channel来接收结果,其中一个channel用于跟踪计算何时完成。我们创建了defer关键字修饰的匿名函数,并在其中将channel关闭,这样一旦这两个channel不再需要时就可以将它们关闭,即外层函数返回时。接下来,我们提供数据并开始耗时的计算任务(在它们自己的goroutine中),每个goroutine都有自己的answer channel和共享的done channel用于通信。
我们本可以使用同一个answer channel,但如果我们这样做,我们就很难知道哪个结果由哪个数据给出(当然,这可能并不重要)。如果我们想要使用同一个channel,并想知道哪个数据会产生哪种结果,我们可以创建一个含有结果字段的struct结构体,例如:type Answer struct{id,answer int}.
随着耗时的计算任务开始于它们各自的goroutine(但被阻塞,因为是非缓冲channel),我们也已经准备好接收结果。for循环的每次迭代,which和result的值都会被重置,select语句会阻塞直至从可以执行的case子句中随机选择一个。如果其中一个answer已就绪,我们就重置which的值并将结果打印出来。如果done channel就绪,我们就将doneCount计数器的值加一,当doneCount的值等于我们要处理的数据数量时,计算任务完成,for循环结束。
一旦跳出for循环,我们就会知道这两个计算任务的goroutine将不再发送数据到它们的channel(因为它们在完成后从自身的无限for循环中跳了出来)。当函数返回时,defer代码块会将channel关闭并释放它们使用的资源。在这之后,垃圾收集器会清理goroutine本身,因为它们已不再执行且channel已被关闭。
Go语言的通信和并发是非常灵活且功能强大的,第7章将专门讨论这些问题。
以上就是本文的全部内容,希望本文的内容对大家的学习或者工作能带来一定的帮助,也希望大家多多支持 码农网
猜你喜欢:- Go语言开发-过程式编程-通信和并发语句
- MySQL 建表语句转 PostgreSQL 建表语句全纪录
- SQL语句优化之JOIN和LEFT JOIN 和 RIGHT JOIN语句的优化
- Python 条件语句
- Python 循环语句
- Python break 语句
本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们。
Imperfect C++中文版
威尔逊 / 荣耀、刘未鹏 / 人民邮电出版社 / 2006-1 / 75.0
汇集实用的C++编程解决方案,C++虽然是一门非凡的语言,但并不完美。Matthew Wilson使用C++十年有余,其间发现C++存在一些固有的限制,需要一些颇具技术性的工作进行弥补。本书不仅指出了C++的缺失,更为你编写健壮、灵活、高效、可维护的代码提供了实用的技术和工具。Wilson向你展示了如何克服C++的复杂性,穿越C++庞大的范式阵列。夺回对代码的控制权,从而获得更理想的结果。一起来看看 《Imperfect C++中文版》 这本书的介绍吧!