Go语言基础天天练之通道操作

超时处理

超时对于一个连接外部资源,或者其它一些需要花费执行时间的操作的程序而言是很重要的。得益于通道和 select,在 Go 中实现超时操作是简洁而优雅的。

package main

import "time"
import "fmt"

func main() {

    // 在我们的例子中,假如我们执行一个外部调用,并在 2 秒后
    // 通过通道 `c1` 返回它的执行结果。
    c1 := make(chan string, 1)
    go func() {
        time.Sleep(time.Second * 2)
        c1 <- "result 1"
    }()

    // 这里是使用 `select` 实现一个超时操作。
    // `res := <- c1` 等待结果,`<-Time.After` 等待超时
    // 时间 1 秒后发送的值。由于 `select` 默认处理第一个
    // 已准备好的接收操作,如果这个操作超过了允许的 1 秒
    // 的话,将会执行超时 case。
    select {
    case res := <-c1:
        fmt.Println(res)
    case <-time.After(time.Second * 1):
        fmt.Println("timeout 1")
    }

    // 如果我允许一个长一点的超时时间 3 秒,将会成功的从 `c2`
    // 接收到值,并且打印出结果。
    c2 := make(chan string, 1)
    go func() {
        time.Sleep(time.Second * 2)
        c2 <- "result 2"
    }()
    select {
    case res := <-c2:
        fmt.Println(res)
    case <-time.After(time.Second * 3):
        fmt.Println("timeout 2")
    }
}

使用这个 select 超时方式,需要使用通道传递结果。这对于一般情况是个好的方式,因为其他重要的 Go 特性是基于通道和select 的。接下来我们就要看到两个例子:timer 和 ticker。

非阻塞通道操作

常规的通过通道发送和接收数据是阻塞的。然而,我们可以使用带一个 default 子句的 select 来实现非阻塞的发送、接收,甚至是非阻塞的多路 select。

package main

import "fmt"

func main() {
    messages := make(chan string)
    signals := make(chan bool)

    // 这里是一个非阻塞接收的例子。如果在 `messages` 中
    // 存在,然后 `select` 将这个值带入 `<-messages` `case`
    // 中。如果不是,就直接到 `default` 分支中。
    select {
    case msg := <-messages:
        fmt.Println("received message", msg)
    default:
        fmt.Println("no message received")
    }

    // 一个非阻塞发送的实现方法和上面一样。
    msg := "hi"
    select {
    case messages <- msg:
        fmt.Println("sent message", msg)
    default:
        fmt.Println("no message sent")
    }

    // 我们可以在 `default` 前使用多个 `case` 子句来实现
    // 一个多路的非阻塞的选择器。这里我们视图在 `messages`
    // 和 `signals` 上同时使用非阻塞的接受操作。
    select {
    case msg := <-messages:
        fmt.Println("received message", msg)
    case sig := <-signals:
        fmt.Println("received signal", sig)
    default:
        fmt.Println("no activity")
    }
}

通道的关闭

关闭一个通道意味着不能再向这个通道发送值了。这个特性可以用来给这个通道的接收方传达工作已将完成的信息。

package main

import "fmt"

// 在这个例子中,我们将使用一个 `jobs` 通道来传递 `main()` 中 Go
// 协程任务执行的结束信息到一个工作 Go 协程中。当我们没有多余的
// 任务给这个工作 Go 协程时,我们将 `close` 这个 `jobs` 通道。
func main() {
    jobs := make(chan int, 5)
    done := make(chan bool)

    // 这是工作 Go 协程。使用 `j, more := <- jobs` 循环的从
    // `jobs` 接收数据。在接收的这个特殊的二值形式的值中,
    // 如果 `jobs` 已经关闭了,并且通道中所有的值都已经接收
    // 完毕,那么 `more` 的值将是 `false`。当我们完成所有
    // 的任务时,将使用这个特性通过 `done` 通道去进行通知。
    go func() {
        for {
            j, more := <-jobs
            if more {
                fmt.Println("received job", j)
            } else {
                fmt.Println("received all jobs")
                done <- true
                return
            }
        }
    }()

    // 这里使用 `jobs` 发送 3 个任务到工作函数中,然后
    // 关闭 `jobs`。
    for j := 1; j <= 3; j++ {
        jobs <- j
        fmt.Println("sent job", j)
    }
    close(jobs)
    fmt.Println("sent all jobs")

    // 我们使用前面学到的通道同步方法等待任务结束。
    <-done
}

通道遍历

在前面的例子中,我们讲过 for 和 range 为基本的数据结构提供了迭代的功能。我们也可以使用这个语法来遍历从通道中取得的值。

package main

import "fmt"

func main() {

    // 我们将遍历在 `queue` 通道中的两个值。
    queue := make(chan string, 2)
    queue <- "one"
    queue <- "two"
    close(queue)

    // 这个 `range` 迭代从 `queue` 中得到的每个值。因为我们
    // 在前面 `close` 了这个通道,这个迭代会在接收完 2 个值
    // 之后结束。如果我们没有 `close` 它,我们将在这个循环中
    // 继续阻塞执行,等待接收第三个值
    for elem := range queue {
        fmt.Println(elem)
    }
}
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值