Go開發(fā)工程師
未來3-5年企業(yè)高性能項目不可替代的語言,從基礎(chǔ)到項目實戰(zhàn)再到重構(gòu),真正從入門到精通
go
語句和通道類型是Go語言的并發(fā)編程理念的最終體現(xiàn)。在第五章,我已經(jīng)詳細(xì)介紹過了通道類型。相比之下,go
語句在用法上要比通道簡單很多。與defer
語句相同,go
語句也可以攜帶一條表達(dá)式語句。注意,go
語句的執(zhí)行會很快結(jié)束,并不會對當(dāng)前流程的進(jìn)行造成阻塞或明顯的延遲。一個簡單的示例如下:
go fmt.Println("Go!")
可以看到,go
語句僅由一個關(guān)鍵字go
和一條表達(dá)式語句構(gòu)成。同樣的,go
語句的執(zhí)行與其攜帶的表達(dá)式語句的執(zhí)行在時間上沒有必然聯(lián)系。這里能夠確定的僅僅是后者會在前者完成之后發(fā)生。在go
語句被執(zhí)行時,其攜帶的函數(shù)(也被稱為go
函數(shù))以及要傳給它的若干參數(shù)(如果有的話)會被封裝成一個實體(即Goroutine),并被放入到相應(yīng)的待運(yùn)行隊列中。Go語言的運(yùn)行時系統(tǒng)會適時的從隊列中取出待運(yùn)行的Goroutine并執(zhí)行相應(yīng)的函數(shù)調(diào)用操作。注意,對傳遞給這里的函數(shù)的那些參數(shù)的求值會在go
語句被執(zhí)行時進(jìn)行。這一點(diǎn)也是與defer
語句類似的。
正是由于go
函數(shù)的執(zhí)行時間的不確定性,所以Go語言提供了很多方法來幫助我們協(xié)調(diào)它們的執(zhí)行。其中最簡單粗暴的方法就是調(diào)用time.Sleep
函數(shù)。請看下面的示例:
package main
import (
"fmt"
)
func main() {
go fmt.Println("Go!")
}
這樣一個命令源碼文件被運(yùn)行時,標(biāo)準(zhǔn)輸出上不會有任何內(nèi)容出現(xiàn)。因為還沒等Go語言運(yùn)行時系統(tǒng)調(diào)度那個go
函數(shù)執(zhí)行,主函數(shù)main
就已經(jīng)執(zhí)行完畢了。函數(shù)main
的執(zhí)行完畢意味著整個程序的執(zhí)行的結(jié)束。因此,這個go
函數(shù)根本就沒有執(zhí)行的機(jī)會。
但是,當(dāng)我們在上述go
語句的后面添加一條對time.Sleep
函數(shù)的調(diào)用語句之后情況就會不同了:
package main import ( "fmt" "time" ) func main() { go fmt.Println("Go!") time.Sleep(100 * time.Millisecond) }
語句time.Sleep(100 * time.Millisecond)
會把main
函數(shù)的執(zhí)行結(jié)束時間向后延遲100毫秒。100毫秒雖短暫,但足夠go
函數(shù)被調(diào)度執(zhí)行的了。上述命令源碼文件在被運(yùn)行時會如我們所愿地在標(biāo)準(zhǔn)輸出上打印出Go!
。
另一個比較紳士的做法是在main
函數(shù)的最后調(diào)用runtime.Gosched
函數(shù)。相應(yīng)的程序版本如下:
package main import ( "fmt" "runtime" ) func main() { go fmt.Println("Go!") runtime.Gosched() }
runtime.Gosched
函數(shù)的作用是讓當(dāng)前正在運(yùn)行的Goroutine(這里是運(yùn)行main
函數(shù)的那個Goroutine)暫時“休息”一下,而讓Go運(yùn)行時系統(tǒng)轉(zhuǎn)去運(yùn)行其它的Goroutine(這里是與go fmt.Println("Go!")
對應(yīng)并會封裝fmt.Println("Go!")
的那個Goroutine)。如此一來,我們就更加精細(xì)地控制了對幾個Goroutine的運(yùn)行的調(diào)度。
當(dāng)然,我們還有其它方法可以滿足上述需求。并且,如果我們需要去左右更多的Goroutine的運(yùn)行時機(jī)的話,下面這種方法也許更合適一些。請看代碼:
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
類型有三個方法可用——Add
、Done
和Wait
。Add
會使其所屬值的一個內(nèi)置整數(shù)得到相應(yīng)增加,Done
會使那個整數(shù)減1
,而Wait
方法會使當(dāng)前Goroutine(這里是運(yùn)行main
函數(shù)的那個Goroutine)阻塞直到那個整數(shù)為0
。這下你應(yīng)該明白上面這個示例所采用的方法了。我們在main
函數(shù)中啟用了三個Goroutine來封裝三個go
函數(shù)。每個匿名函數(shù)的最后都調(diào)用了wg.Done
方法,并以此表達(dá)當(dāng)前的go
函數(shù)會立即執(zhí)行結(jié)束的情況。當(dāng)這三個go
函數(shù)都調(diào)用過wg.Done
函數(shù)之后,處于main
函數(shù)最后的那條wg.Wait()
語句的阻塞作用將會消失,main
函數(shù)的執(zhí)行將立即結(jié)束。
與go
語句、go
函數(shù)以及承載其運(yùn)行的Goroutine相關(guān)的話題其實還有很多。不過由于篇幅等原因,我的講述就先到此為止。如果大家對這方面的內(nèi)容感興趣的話請參看《Go并發(fā)編程實戰(zhàn)》這本書。這里面會有非常詳盡的介紹。
在命令源碼文件index.go中的main
函數(shù)中,我編寫了三條go
語句。其中的go
函數(shù)會分別向標(biāo)準(zhǔn)輸出打印一個整數(shù)。我希望打印的最終結(jié)果是
1 2 3
請你對其中的代碼做適當(dāng)?shù)男薷膩頋M足這個需求。
修改后的文件內(nèi)容可以是:
package main import ( "fmt" ) func main() { ch1 := make(chan int, 1) ch2 := make(chan int, 1) ch3 := make(chan int, 3) go func() { fmt.Println("1") ch1 <- 1 }() go func() { <-ch1 fmt.Println("2") ch2 <- 2 }() go func() { <-ch2 fmt.Println("3") ch3 <- 3 }() <-ch3 }
請驗證,完成請求
由于請求次數(shù)過多,請先驗證,完成再次請求
打開微信掃碼自動綁定
綁定后可得到
使用 Ctrl+D 可將課程添加到書簽
舉報