Day12 Select

Select vs Switch

select与switch都是透过case的方式来处理,但两者却大不同,并完全不相容。

以下方code为举例

package main

import "fmt"

var (
    i interface{}
)

func convert(i interface{}) {
    switch t := i.(type) {
    case int:
        fmt.Println("i is interger", t)
    case string:
        fmt.Println("i is string", t)
    case float64:
        fmt.Println("i is float64", t)
    default:
        fmt.Println("type not found")
    }
}

func main() {
    i = 100
    convert(i)
    i = float64(45.55)
    convert(i)
    i = "foo"
    convert(i)
    convert(float32(10.0))
}

运行後可得以下结果

i is interger 100
i is float64 45.55
i is string foo
type not found

switch可以配合各种类型与型别的接口操作,但是select只能接channel否则会出错,default 会直接执行,所以没有 default 的 select 就会遇到 blocking,假设没有送 value 进去 Channel 就会造成 panic,底下拿几个实际例子来解说。

Select

select使用的范例如下

package main

import "fmt"

func main() {
    ch := make(chan int, 1)

    ch <- 1
    select {
    case <-ch:
        fmt.Println("pull data")
    default:
        fmt.Println("default")
    }
}

运行後可得以下结果

pull data

Random Select

同一个channel在select会随机选取,底下看个例子:

package main

import "fmt"

func main() {
    ch := make(chan int, 1)

    ch <- 1
    select {
    case <-ch:
        fmt.Println("random 01")
    case <-ch:
        fmt.Println("random 02")
    }
}

执行後会发现有时候拿到 random 01 有时候拿到 random 02,这就是 select 的特性之一,case 是随机选取,所以当 select 有两个 channel 以上时,如果同时对全部 channel 送资料,则会随机选取到不同的 Channel。而上面有提到另一个特性『假设没有送 value 进去 Channel 就会造成 panic』,拿上面例子来改:

package main

import "fmt"

func main() {
    ch := make(chan int, 1)

    select {
    case <-ch:
        fmt.Println("random 01")
    case <-ch:
        fmt.Println("random 02")
    }
}

执行後可得以下结果

atal error: all goroutines are asleep - deadlock!

goroutine 1 [select]:
main.main()
	/tmp/sandbox3304355956/prog.go:8 +0x65

执行後会发现变成 deadlock,造成 main 主程序爆炸,这时候可以直接用 default 方式解决此问题:

package main

import "fmt"

func main() {
    ch := make(chan int, 1)

    select {
    case <-ch:
        fmt.Println("random 01")
    case <-ch:
        fmt.Println("random 02")
    default:
        fmt.Println("exit")
    }
}

执行後可得以下结果

exit

主程序 main 就不会因为读不到 channel value 造成整个程序 deadlock。

Timeout in select

用 select 读取 channle 时,一定会实作超过一定时间後就做其他事情,而不是一直 blocking 在 select 内。底下是简单的例子:

package main

import (
    "fmt"
    "time"
)

func main() {
    timeout := make(chan bool, 1)
    go func() {
        time.Sleep(2 * time.Second)
        timeout <- true
    }()
    ch := make(chan int)
    select {
    case <-ch:
    case <-timeout:
        fmt.Println("timeout 01")
    }
}

执行後可得以下结果

timeout 01

建立 timeout channel,让其他地方可以透过 trigger timeout channel 达到让 select 执行结束,也或者有另一个写法是透握 time.After 机制

select {
    case <-ch:
    case <-timeout:
        fmt.Println("timeout 01")
    case <-time.After(time.Second * 1):
        fmt.Println("timeout 02")
    }

可以注意 time.After 是回传 chan time.Time,所以执行 select 超过一秒时,就会输出 timeout 02。

Checking size of channel

package main

import (
    "fmt"
)

func main() {
    ch := make(chan int, 1)
    ch <- 1
    select {
    case ch <- 2:
        fmt.Println("channel value is", <-ch)
        fmt.Println("channel value is", <-ch)
    default:
        fmt.Println("channel blocking")
    }
}

运算後可得以下结果

channel blocking

先宣告 buffer size 为 1 的 channel,先丢值把 channel 填满。这时候可以透过 select + default 方式来确保 channel 是否已满,上面例子会输出 channel blocking,我们再把程序改成底下

package main

import (
    "fmt"
)

func main() {
    ch := make(chan int, 2)
    ch <- 1
    select {
    case ch <- 2:
        fmt.Println("channel value is", <-ch)
        fmt.Println("channel value is", <-ch)
    default:
        fmt.Println("channel blocking")
    }
}

运算後可得以下结果

channel value is 1
channel value is 2

把 buffer size 改为 2 後,就可以继续在塞 value 进去 channel 了

select for loop

如果你有多个 channel 需要读取,而读取是不间断的,就必须使用 for + select 机制来实现

package main

import (
    "fmt"
    "time"
)

func main() {
    i := 0
    ch := make(chan string, 0)
    defer func() {
        close(ch)
    }()

    go func() {
    LOOP:
        for {
            time.Sleep(1 * time.Second)
            fmt.Println(time.Now().Unix())
            i++

            select {
            case m := <-ch:
                println(m)
                break LOOP
            default:
            }
        }
    }()

    time.Sleep(time.Second * 4)
    ch <- "stop"
}

运行後可得以下结果

1257894001
1257894002
1257894003
1257894004

其实把 default 拿掉也可以达到目的

select {
case m := <-ch:
    println(m)
    break LOOP

当没有值送进来时,就会一直停在 select 区段,所以其实没有 default 也是可以正常运作的,而要结束 for 或 select 都需要透过 break 来结束,但是要在 select 区间直接结束掉 for 回圈,只能使用 break variable 来结束,这边是大家需要注意的地方。

Summary

这章节主要延续着Channel去讲述select的使用方式,希望能让大家在使用channel的同时可以避开许许多多的error。


<<:  Ruby on Rails Model 验证及回呼

>>:  Day12 探讨urls(2)

Leetcode 挑战 Day 02 [9. Palindrome Number]

9. Palindrome Number Palindrome Number中文意思即是回文数字,这...

What is World Smile Day?

What is World Smile Day? The name itself may give ...

iOS APP 开发 OC 第九天,网路请求原理

tags: OC 30 day 因为工作的需求,今天跳级来写写网路请求。 NSURLConnecti...

D26 如何用 Apps Script 自动化地创造与客制 Google Sheet?(三)依照范本大量复制试算表

今天的目标: 要怎麽样依照范本复制并改动 Google Sheet,并一次性地的将结果搜集到同一份 ...

Swift纯Code之旅 Day28. 「新增闹钟功能(1) - Struct使用、取得UIDatePicker值」

前言 如果只有画面像的话,那也太弱了吧! 赶紧来实作新增闹钟的功能,做完拿去炫耀给边身边的人看! 实...