内容简介: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 语句
本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们。
The Filter Bubble
Eli Pariser / Penguin Press / 2011-5-12 / GBP 16.45
In December 2009, Google began customizing its search results for each user. Instead of giving you the most broadly popular result, Google now tries to predict what you are most likely to click on. Ac......一起来看看 《The Filter Bubble》 这本书的介绍吧!