1. 程式人生 > >【更多流程控制】4. go語句初探

【更多流程控制】4. go語句初探

go語句初探

    go語句和通道型別是Go語言的併發程式設計理念的最終體現。在第五章,我已經詳細介紹過了通道型別。相比之下,go語句在用法上要比通道簡單很多。與defer語句相同,go語句也可以攜帶一條表示式語句。注意,go語句的執行會很快結束,並不會對當前流程的進行造成阻塞或明顯的延遲。一個簡單的示例如下:

go fmt.Println("Go!")

    可以看到,go語句僅由一個關鍵字go和一條表示式語句構成。同樣的,go語句的執行與其攜帶的表示式語句的執行在時間上沒有必然聯絡。這裡能夠確定的僅僅是後者會在前者完成之後發生。在go

語句被執行時,其攜帶的函式(也被稱為go函式)以及要傳給它的若干引數(如果有的話)會被封裝成一個實體(即Goroutine),並被放入到相應的待執行佇列中。Go語言的執行時系統會適時的從佇列中取出待執行的Goroutine並執行相應的函式呼叫操作。注意,對傳遞給這裡的函式的那些引數的求值會在go語句被執行時進行。這一點也是與defer語句類似的。
  
    正是由於go函式的執行時間的不確定性,所以Go語言提供了很多方法來幫助我們協調它們的執行。其中最簡單粗暴的方法就是呼叫time.Sleep函式。請看下面的示例:

package main

import (
    "fmt"
)

func main() {
    go fmt.Println("Go!")
}  

    這樣一個命令原始碼檔案被執行時,標準輸出上不會有任何內容出現。因為還沒等Go語言執行時系統排程那個go函式執行,主函式main就已經執行完畢了。函式main的執行完畢意味著整個程式的執行的結束。因此,這個go函式根本就沒有執行的機會。
  
  但是,當我們在上述go語句的後面新增一條對time.Sleep函式的呼叫語句之後情況就會不同了:

package main

import (
    "fmt"
    "time"
)

func main() {
    go fmt.Println("Go!")
    time.Sleep(100 * time.Millisecond)
}

    語句time.Sleep(100 * time.Millisecond)會把main函式的執行結束時間向後延遲100毫秒。100毫秒雖短暫,但足夠go函式被排程執行的了。上述命令原始碼檔案在被執行時會如我們所願地在標準輸出上打印出Go!

  
    另一個比較紳士的做法是在main函式的最後呼叫runtime.Gosched函式。相應的程式版本如下:

package main

import (
    "fmt"
    "runtime"
)

func main() {
    go fmt.Println("Go!")
    runtime.Gosched()
}

   runtime.Gosched函式的作用是讓當前正在執行的Goroutine(這裡是執行main函式的那個Goroutine)暫時“休息”一下,而讓Go執行時系統轉去執行其它的Goroutine(這裡是與go fmt.Println("Go!")對應並會封裝fmt.Println("Go!")的那個Goroutine)。如此一來,我們就更加精細地控制了對幾個Goroutine的執行的排程。
  
    當然,我們還有其它方法可以滿足上述需求。並且,如果我們需要去左右更多的Goroutine的執行時機的話,下面這種方法也許更合適一些。請看程式碼:

package main

import (
    "fmt"
    "sync"
)

func main() {
    var wg sync.WaitGroup
    wg.Add(3)
    go func() {
        fmt.Println("Go!")
        wg.Done()
    }()
    go func() {
        fmt.Println("Go!")
        wg.Done()
    }()
    go func() {
        fmt.Println("Go!")
        wg.Done()
    }()
    wg.Wait()
}

    sync.WaitGroup型別有三個方法可用——AddDoneWaitAdd會使其所屬值的一個內建整數得到相應增加,Done會使那個整數減1,而Wait方法會使當前Goroutine(這裡是執行main函式的那個Goroutine)阻塞直到那個整數為0。這下你應該明白上面這個示例所採用的方法了。我們在main函式中啟用了三個Goroutine來封裝三個go函式。每個匿名函式的最後都呼叫了wg.Done方法,並以此表達當前的go函式會立即執行結束的情況。當這三個go函式都呼叫過wg.Done函式之後,處於main函式最後的那條wg.Wait()語句的阻塞作用將會消失,main函式的執行將立即結束。
  
    與go語句、go函式以及承載其執行的Goroutine相關的話題其實還有很多。不過由於篇幅等原因,我的講述就先到此為止。如果大家對這方面的內容感興趣的話請參看《Go併發程式設計實戰》這本書。這裡面會有非