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章将专门讨论这些问题。

目录


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

查看所有标签

猜你喜欢:

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

Python带我起飞

Python带我起飞

李金洪 / 电子工业出版社 / 2018-6 / 79

《Python带我起飞——入门、进阶、商业实战》针对Python 3.5 以上版本,采用“理论+实践”的形式编写,通过大量的实例(共42 个),全面而深入地讲解“Python 基础语法”和“Python 项目应用”两方面内容。书中的实例具有很强的实用性,如对医疗影像数据进行分析、制作爬虫获取股票信息、自动化实例、从一组看似混乱的数据中找出规律、制作人脸识别系统等。 《Python带我起飞——......一起来看看 《Python带我起飞》 这本书的介绍吧!

URL 编码/解码
URL 编码/解码

URL 编码/解码

XML 在线格式化
XML 在线格式化

在线 XML 格式化压缩工具

HEX HSV 转换工具
HEX HSV 转换工具

HEX HSV 互换工具