Go语言开发-过程式编程-通信和并发语句-Select语句

栏目: Go · 发布时间: 5年前

内容简介: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章将专门讨论这些问题。

目录


以上就是本文的全部内容,希望本文的内容对大家的学习或者工作能带来一定的帮助,也希望大家多多支持 码农网

查看所有标签

猜你喜欢:

本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们

Imperfect C++中文版

Imperfect C++中文版

威尔逊 / 荣耀、刘未鹏 / 人民邮电出版社 / 2006-1 / 75.0

汇集实用的C++编程解决方案,C++虽然是一门非凡的语言,但并不完美。Matthew Wilson使用C++十年有余,其间发现C++存在一些固有的限制,需要一些颇具技术性的工作进行弥补。本书不仅指出了C++的缺失,更为你编写健壮、灵活、高效、可维护的代码提供了实用的技术和工具。Wilson向你展示了如何克服C++的复杂性,穿越C++庞大的范式阵列。夺回对代码的控制权,从而获得更理想的结果。一起来看看 《Imperfect C++中文版》 这本书的介绍吧!

CSS 压缩/解压工具
CSS 压缩/解压工具

在线压缩/解压 CSS 代码

RGB HSV 转换
RGB HSV 转换

RGB HSV 互转工具

HSV CMYK 转换工具
HSV CMYK 转换工具

HSV CMYK互换工具