莱莱不是菜菜 發表於 2019-11-9 12:03:00

Go Select使用

<p>&nbsp;Go Select使用</p>
<p>Go中的select和channel配合使用,通过select可以监听多个channel的I/O读写事件,当 IO操作发生时,触发相应的动作。</p>
<h3>基本用法</h3>
<div class="cnblogs_Highlighter">
<pre class="brush:go;gutter:true;">//select基本用法
select {
case &lt;- chan1:
// 如果chan1成功读到数据,则进行该case处理语句
case chan2 &lt;- 1:
// 如果成功向chan2写入数据,则进行该case处理语句
default:
// 如果上面都没有成功,则进入default处理流程</pre>
</div>
<p>使用规则</p>
<div class="cnblogs_Highlighter">
<pre class="brush:go;gutter:true;">1.如果没有default分支,select会阻塞在多个channel上,对多个channel的读/写事件进行监控。
2.如果有一个或多个IO操作可以完成,则Go运行时系统会随机的选择一个执行,否则的话,如果有default分支,则执行default分支语句,如果连default都没有,则select语句会一直阻塞,直到至少有一个IO操作可以进行。   </pre>
</div>
<h3>快速返回</h3>
<p>同时监听不同的channel,做同一件工作,可以最快的返回结果。</p>
<div class="cnblogs_Highlighter">
<pre class="brush:go;gutter:true;">package main

import (
        "fmt"
        "github.com/kirinlabs/HttpRequest"
)

func main() {
        ch1 := make(chan int)
        ch2 := make(chan int)
        ch3 := make(chan int)
        go Getdata("https://www.baidu.com",ch1)
        go Getdata("https://www.baidu.com",ch2)
        go Getdata("https://www.baidu.com",ch3)
        select{
                case v:=&lt;- ch1:
                        fmt.Println(v)
                case v:=&lt;- ch2:
                        fmt.Println(v)
                case v:=&lt;- ch3:
                        fmt.Println(v)
        }
}

func Getdata(url string,ch chan int){
        req,err := HttpRequest.Get(url)
        if err != nil{

        }else{
                ch &lt;- req.StatusCode()
        }
}</pre>
</div>
<h3>随机返回</h3>
<p>同时监控不同的channel,配上default,select也不会阻塞。</p>
<div class="cnblogs_Highlighter">
<pre class="brush:go;gutter:true;">package main

import (
        "fmt"
        "github.com/kirinlabs/HttpRequest"
)

func main() {
        ch1 := make(chan int)
        ch2 := make(chan int)
        ch3 := make(chan int)
        go func(){
                for {
                        Getdata("https://www.baidu.com", ch1)
                        Getdata("https://cn.bing.com", ch2)
                        Getdata("https://cn.bing.com", ch3)
                }
        }()
        go func(){
                for {
                        select {
                                case v := &lt;-ch1:
                                        fmt.Println("信道1的结果:",v)
                                case v := &lt;-ch2:
                                        fmt.Println("信道2的结果:",v)
                                case v := &lt;-ch3:
                                        fmt.Println("信道3的结果:",v)
                                default:
                                        continue
                        }
                }
        }()
        select{}
}

func Getdata(url string,ch chan int){
        req,err := HttpRequest.Get(url)
        if err != nil{

        }else{
                ch &lt;- req.StatusCode()
        }
}</pre>
</div>
<h3>通过select来检测channel的关闭事件</h3>
<div class="cnblogs_Highlighter">
<pre class="brush:go;gutter:true;">func TestSelect1() {
    start := time.Now()
    c := make(chan interface{})

    go func() {
      time.Sleep(2*time.Second)
      close(c)
    }()

    fmt.Println("Blocking on read...")
    select {
    case &lt;-c:
      fmt.Printf("Unblocked %v later.\n", time.Since(start))
    }
}</pre>
</div>
<p>注意:当close channel时,读取channel的一方会从channel中读取到value,false,此时的value一般情况下为nil。 <br>该例子也可以用来通知当不使用channel时,关闭channel的情况。 </p>
<h3>通过channel通知,从而退出死循环</h3>
<div class="cnblogs_Highlighter">
<pre class="brush:go;gutter:true;">func TestExitLoop() {
    done := make(chan interface{})

    go func() {
      time.Sleep(2*time.Second)
      close(done)
    }()

    workCounter := 0
loop:
    for {
      select {
      case &lt;-done:
            break loop
      default:
      }

      // Simulate work
      workCounter++
      time.Sleep(1*time.Second)
    }

    fmt.Printf("在通知退出循环时,执行了%d次.\n", workCounter)
}
</pre>
</div>
<p>启动一个goroutine,该goroutine在2s后,关闭channel。此时,主协程会在select中的case &lt;-done分支中得到通知,跳出死循环。而在此之前,会执行default分支的代码,这里是什么都不做。</p>
<h3>超时机制</h3>
<div class="cnblogs_Highlighter">
<pre class="brush:go;gutter:true;">package main
import (
        "fmt"
        "time"
)
func main() {
        ch := make(chan int)
        quit := make(chan bool)
        //新开一个协程
        go func() {
                for {
                        select {
                        case num := &lt;-ch:
                                fmt.Println("num = ", num)
                        case &lt;-time.After(3 * time.Second):
                                fmt.Println("超时")
                                quit &lt;- true
                        }
                }
        }()
        for i := 0; i &lt; 5; i++ {
                ch &lt;- i
                time.Sleep(time.Second)
        }
        &lt;-quit
        fmt.Println("程序结束")
}</pre>
</div>
<h3>死锁与默认情况</h3>
<div class="cnblogs_Highlighter">
<pre class="brush:go;gutter:true;">package main

func main() {
    ch := make(chan string)   
    select {   
      case &lt;-ch:
    }
}</pre>
</div>
<p>上面的程序中,我们在第 4 行创建了一个信道&nbsp;<code>ch</code>。我们在&nbsp;<code>select</code>&nbsp;内部(第 6 行),试图读取信道 ch。由于没有 Go 协程向该信道写入数据,因此&nbsp;<code>select</code>&nbsp;语句会一直阻塞,导致死锁。该程序会触发运行时&nbsp;<code>panic</code>,报错信息如下:</p>
<pre><code>fatal error: all goroutines are asleep - deadlock!

goroutine 1 : &nbsp;
main.main() &nbsp;
&nbsp; &nbsp;/tmp/sandbox416567824/main.go:6 +0x80</code></pre>
<p>如果存在默认情况,就不会发生死锁,因为在没有其他 case 准备就绪时,会执行默认情况。我们用默认情况重写后,程序如下:</p>
<div class="cnblogs_Highlighter">
<pre class="brush:go;gutter:true;">package main

import "fmt"

func main() {
    ch := make(chan string)   
    select {   
      case &lt;-ch:   
      default:
            fmt.Println("default case executed")
    }
}  </pre>
</div>
<p>以上程序会输出:</p>
<pre><code>default case executed</code></pre>
<p>如果&nbsp;<code>select</code>&nbsp;只含有值为&nbsp;<code>nil</code>&nbsp;的信道,也同样会执行默认情况。</p>
<div class="cnblogs_Highlighter">
<pre class="brush:go;gutter:true;">package main

import "fmt"

func main() {
    var ch chan string
    select {   
      case v := &lt;-ch:
            fmt.Println("received value", v)   
      default:
            fmt.Println("default case executed")

    }
}</pre>
</div>
<p>在线运行程序</p>
<p>在上面程序中,<code>ch</code>&nbsp;等于&nbsp;<code>nil</code>,而我们试图在&nbsp;<code>select</code>&nbsp;中读取&nbsp;<code>ch</code>(第 8 行)。如果没有默认情况,<code>select</code>&nbsp;会一直阻塞,导致死锁。由于我们在&nbsp;<code>select</code>内部加入了默认情况,程序会执行它,并输出:</p>
<pre><code>default case executed<br></code></pre>
<h3>空 select</h3>
<div class="cnblogs_Highlighter">
<pre class="brush:go;gutter:true;">package main
func main() {
    select {}
}</pre>
</div>
<p>我们已经知道,除非有&nbsp;<code>case</code>&nbsp;执行,<code>select</code>&nbsp;语句就会一直阻塞着。在这里,<code>select</code>&nbsp;语句没有任何&nbsp;<code>case</code>,因此它会一直阻塞,导致死锁。该程序会触发&nbsp;<code>panic</code>,输出如下:</p>
<pre><code>fatal error: all goroutines are asleep - deadlock!

goroutine 1 : &nbsp;
main.main() &nbsp;
&nbsp; &nbsp;/tmp/sandbox299546399/main.go:4 +0x20<br></code></pre><br><br>
来源:https://www.cnblogs.com/-wenli/p/11824932.html
頁: [1]
查看完整版本: Go Select使用