美文网首页
GO学习笔记(18) - 并发编程(3)- Select与Cha

GO学习笔记(18) - 并发编程(3)- Select与Cha

作者: 卡门001 | 来源:发表于2021-07-09 02:23 被阅读0次

本文主要讲解Go并发编程之Select

目录

  • 介绍
  • 基础语法
  • timeout
  • 综合实例

select 是 Go 中的一个控制结构,类似于用于通信的 switch 语句。每个 case 必须是一个通信操作,要么是发送要么是接收,它的case涉及到channel有关的I/O操作。

或者换一种说法,select就是用来监听和channel有关的IO操作,当 IO 操作发生时,触发相应的动作。

Go 语言的 select 语句借鉴自 Unix 的 select() 函数,在 Unix 中,可以通过调用 select() 函数来监控一系列的文件句柄,一旦其中一个文件句柄发生了 IO 动作,该 select() 调用就会被返回(C 语言中就是这么做的),后来该机制也被用于实现高并发的 Socket 服务器程序。Go 语言直接在语言级别支持 select关键字,用于处理并发编程中通道之间异步 IO 通信问题。

select基础语法

select {
    case <-ch1:
        // 如果从 ch1 信道成功接收数据,则执行该分支代码
    case ch2 <- 1:
        // 如果成功向 ch2 信道成功发送数据,则执行该分支代码
    default:
        // 如果上面都没有成功,则进入 default 分支处理流程,有了default非阻塞式
}
  • select语句只能用于信道的读写操作
  • select中的case条件(非阻塞)是并发执行的,select会选择先操作成功的那个case条件去执行。如果多个同时返回,则随机选择一个执行,此时将无法保证执行顺序。
  • case条件语句中如果存在信道值为nil的读写操作,则该分支将被忽略。
  • 如果有超时条件语句,判断逻辑为如果在这个时间段内一直没有满足条件的case,则执行这个超时case。一般用超时语句代替了default语句
  • 对于空的select{},会引起死锁
  • 对于for中的select{}, 也有可能会引起cpu占用过高的问题

timeout

select有很重要的一个应用就是超时处理。 如果没有case需要处理,select语句就会一直阻塞着。这时候我们可能就需要一个超时操作,用来处理超时的情况。

import "time"
import "fmt"
func main() {
    c1 := make(chan string, 1)
    go func() {
        time.Sleep(time.Second * 2)
        c1 <- "result 1"
    }()
    select {
    case res := <-c1:
        fmt.Println(res)
    case <-time.After(time.Second * 1):
        fmt.Println("timeout 1")
    }
}

Timer和Ticker

Timer

timer是一个定时器,代表未来的一个单一事件,你可以告诉timer你要等待多长时间,它提供一个Channel,在将来的那个时间那个Channel提供了一个时间值。下面的例子中第二行会阻塞2秒钟左右的时间,直到时间到了才会继续执行。

timer1 := time.NewTimer(time.Second * 2)
<-timer1.C
fmt.Println("Timer 1 expired")

当然如果你只是想单纯的等待的话,可以使用time.Sleep来实现。
还可以使用timer.Stop来停止计时器。

timer2 := time.NewTimer(time.Second)
go func() {
    <-timer2.C
    fmt.Println("Timer 2 expired")
}()
stop2 := timer2.Stop()
if stop2 {
    fmt.Println("Timer 2 stopped")
}

ticker

ticker是一个定时触发的计时器,它会以一个间隔(interval)往Channel发送一个事件(当前时间),而Channel的接收者可以以固定的时间间隔从Channel中读取事件。下面的例子中ticker每500毫秒触发一次,你可以观察输出的时间。

ticker := time.NewTicker(time.Millisecond * 500)
go func() {
    for t := range ticker.C {
        fmt.Println("Tick at", t)
    }
}()

类似timer, ticker也可以通过Stop方法来停止。一旦它停止,接收者不再会从channel中接收数据了。

综合例子

从该例子中可以学到

  • select的使用:有了default代表非阻塞模式
  • 定时器的使用
  • 在select中使用了Nil channel
package main

import (
    "fmt"
    "math/rand"
    "time"
)


func  worker(id int,c chan  int)  {
    for n := range c {  //待到c 被close时,退出
        time.Sleep(time.Second * 1) //收到的数据消费太慢了,会被冲掉。
        fmt.Printf("worker %d receiver %d \n", id, n)
    }
}

func createWorker(id int)  chan  int {
    c := make(chan int)
    //处理
    go worker(id,c)
    return c
}

func generator() chan int{
    out := make(chan int)
    go func() {
        i := 0
        for{
            time.Sleep(time.Duration(rand.Intn(1500)) * time.Millisecond)
            out <- i
            i ++
        }
    }()
    return out
}
func main() {
    //channel内部收发数据为阻塞式的
    //如果要实现非阻塞式的,用select+default
    var c1, c2 = generator(),generator() //c1 与 c2 nil vs nil
    var worker = createWorker(0) //nil channel
    n:=0
    var values []int
    tm := time.After(10 * time.Second)
    tick := time.Tick(time.Second)
    for {
        var activeValue int
        var activeWorker chan int
        if len(values) > 0 {
            activeWorker = worker
            activeValue = values[0]
        }
        select {
        case n = <-c1: //从c1成功接收数据
            values = append(values,n)
        case n = <-c2: //从c2成功接收数据
            values = append(values,n)
        case activeWorker <- activeValue:
            values = values[1:]
        case <-time.After(800*time.Millisecond): //在80秒中未送入数据,打印timeout。
            fmt.Println("timeout")
        case <- tick: //每隔1秒,打印队列中的长度
            fmt.Println("queue len = ",len(values))
        case <-tm:
            fmt.Println("Bye") //总时长超过定义的时间,程序退出
            return
        }
    }
}

相关文章

网友评论

      本文标题:GO学习笔记(18) - 并发编程(3)- Select与Cha

      本文链接:https://www.haomeiwen.com/subject/occwultx.html