協(xié)程(Goroutines)
在Go語言中,每一個并發(fā)的執(zhí)行單元叫作一個goroutine。,我們只需要通過 go 關(guān)鍵字來開啟 goroutine 即可。goroutine 是輕量級線程,goroutine 的調(diào)度是由 Golang 運行時進行管理的。
goroutine 語法格式:
go 函數(shù)名( 參數(shù)列表 )
示例:
package mainimport “fmt”func f(from string) {for i := 0; i < 3; i++ {fmt.Println(from, ":", i)}}func main() {// 假設(shè)我們有一個函數(shù)叫做 f(s)。// 我們使用一般的方式調(diào)并同時運行。f("direct")// 使用 go f(s) 在一個 Go 協(xié)程中調(diào)用這個函數(shù)。// 這個新的 Go 協(xié)程將會并行的執(zhí)行這個函數(shù)調(diào)用。go f("goroutine")// 你也可以為匿名函數(shù)啟動一個 Go 協(xié)程。go func(msg string) {fmt.Println(msg)}("going")// 現(xiàn)在這兩個 Go 協(xié)程在獨立的 Go 協(xié)程中異步的運行,所以我們需要等它們執(zhí)行結(jié)束。// 這里的 Scanln 代碼需要我們在程序退出前按下任意鍵結(jié)束。var input stringfmt.Scanln(&input)fmt.Println("done")// 當(dāng)我們運行這個程序時,將首先看到阻塞式調(diào)用的輸出,然后是兩個 Go 協(xié)程的交替輸出。// 這種交替的情況表示 Go 運行時是以異步的方式運行協(xié)程的。}
通道(channel)
如果說goroutine是Go語言程序的并發(fā)體的話,那么channels則是它們之間的通信機制。通道(channel)是用來傳遞數(shù)據(jù)的一個數(shù)據(jù)結(jié)構(gòu)。
通道可用于兩個 goroutine 之間通過傳遞一個指定類型的值來同步運行和通訊。操作符 <- 用于指定通道的方向,發(fā)送或接收。如果未指定方向,則為雙向通道。
ch <- v // 把 v 發(fā)送到通道 chv := <-ch // 從 ch 接收數(shù)據(jù) // 并把值賦給 v
聲明一個通道很簡單,我們使用chan關(guān)鍵字即可,通道在使用前必須先創(chuàng)建:
ch := make(chan int)
示例:
package mainimport (“fmt”)// 通道 是連接多個 Go 協(xié)程的管道。// 你可以從一個 Go 協(xié)程將值發(fā)送到通道,然后在別的 Go 協(xié)程中接收。func main() {// 使用 make(chan val-type) 創(chuàng)建一個新的通道。// 通道類型就是他們需要傳遞值的類型。messages := make(chan string)// 使用 channel <- 語法 發(fā)送 一個新的值到通道中。// 這里我們在一個新的 Go 協(xié)程中發(fā)送 "ping" 到上面創(chuàng)建的messages 通道中。go func() {messages <- "ping"}()// 使用 <-channel 語法從通道中 接收 一個值。// 這里將接收我們在上面發(fā)送的 "ping" 消息并打印出來。msg := <-messagesfmt.Println(msg)// 我們運行程序時,通過通道,消息 "ping" 成功的從一個 Go 協(xié)程傳到另一個中。// 默認(rèn)發(fā)送和接收操作是阻塞的,直到發(fā)送方和接收方都準(zhǔn)備完畢。// 這個特性允許我們,不使用任何其它的同步操作,來在程序結(jié)尾等待消息 "ping"。}
通道緩沖區(qū)
通道可以設(shè)置緩沖區(qū),通過 make 的第二個參數(shù)指定緩沖區(qū)大小:
ch := make(chan int, 100)
帶緩沖區(qū)的通道允許發(fā)送端的數(shù)據(jù)發(fā)送和接收端的數(shù)據(jù)獲取處于異步狀態(tài),就是說發(fā)送端發(fā)送的數(shù)據(jù)可以放在緩沖區(qū)里面,可以等待接收端去獲取數(shù)據(jù),而不是立刻需要接收端去獲取數(shù)據(jù)。
不過由于緩沖區(qū)的大小是有限的,所以還是必須有接收端來接收數(shù)據(jù)的,否則緩沖區(qū)一滿,數(shù)據(jù)發(fā)送端就無法再發(fā)送數(shù)據(jù)了。
注意:如果通道不帶緩沖,發(fā)送方會阻塞直到接收方從通道中接收了值。如果通道帶緩沖,發(fā)送方則會阻塞直到發(fā)送的值被拷貝到緩沖區(qū)內(nèi);如果緩沖區(qū)已滿,則意味著需要等待直到某個接收方獲取到一個值。接收方在有值可以接收之前會一直阻塞。
示例:
package mainimport “fmt”// 默認(rèn)通道是 無緩沖 的,這意味著只有在對應(yīng)的接收(<- chan)通道準(zhǔn)備好接收時,才允許進行發(fā)送(chan <-)。// 可緩存通道允許在沒有對應(yīng)接收方的情況下,緩存限定數(shù)量的值。func main() {// 這里我們 make 了一個通道,最多允許緩存 2 個值。messages := make(chan string, 2)// 因為這個通道是有緩沖區(qū)的,即使沒有一個對應(yīng)的并發(fā)接收方,我們?nèi)匀豢梢园l(fā)送這些值。messages <- "buffered"messages <- "channel"// 然后我們可以像前面一樣接收這兩個值。fmt.Println(<-messages)fmt.Println(<-messages)}
同步實現(xiàn)
我們可以通過channel實現(xiàn)同步,如下:
package mainimport “fmt”import “time”// 我們可以使用通道來同步 Go 協(xié)程間的執(zhí)行狀態(tài)。// 這里是一個使用阻塞的接受方式來等待一個 Go 協(xié)程的運行結(jié)束。func worker(done chan bool) {// 這是一個我們將要在 Go 協(xié)程中運行的函數(shù)。// done 通道將被用于通知其他 Go 協(xié)程這個函數(shù)已經(jīng)工作完畢。fmt.Print(“working…”)time.Sleep(time.Second)fmt.Println(“done”)// 發(fā)送一個值來通知我們已經(jīng)完工啦。done <- true}func main() {// 運行一個 worker Go協(xié)程,并給予用于通知的通道。done := make(chan bool, 1)go worker(done)// 程序?qū)⒃诮邮盏酵ǖ乐?worker 發(fā)出的通知前一直阻塞。<-done// 如果你把 <- done 這行代碼從程序中移除,程序甚至?xí)?worker還沒開始運行時就結(jié)束了。}
通道方向示例
package mainimport “fmt”// 當(dāng)使用通道作為函數(shù)的參數(shù)時,你可以指定這個通道是不是只用來發(fā)送或者接收值。// 這個特性提升了程序的類型安全性。func ping(pings chan <- string, msg string) {// ping 函數(shù)定義了一個只允許發(fā)送數(shù)據(jù)的通道。// 嘗試使用這個通道來接收數(shù)據(jù)將會得到一個編譯時錯誤。pings <- msg}func pong(pings <-chan string, pongs chan <- string) {// pong 函數(shù)允許通道(pings)來接收數(shù)據(jù),另一通道(pongs)來發(fā)送數(shù)據(jù)。msg := <-pingspongs <- msg}func main() {pings := make(chan string, 1)pongs := make(chan string, 1)ping(pings, "passed message")pong(pings, pongs)fmt.Println(<-pongs)}
Go 遍歷通道與關(guān)閉通道
Go 通過 range 關(guān)鍵字來實現(xiàn)遍歷讀取到的數(shù)據(jù),類似于與數(shù)組或切片。格式如下:
v, ok := <-ch
如果通道接收不到數(shù)據(jù)后 ok 就為 false,這時通道就可以使用 close() 函數(shù)來關(guān)閉。
通道選擇器(select)
select {case <-ch1: // …case x := <-ch2: // …use x…case ch3 <- y: // …default: // …}
select語句的一般形式。和switch語句稍微有點相似,也會有幾個case和最后的default選擇支。每一個case代表一個通信操作(在某個channel上進行發(fā)送或者接收)并且會包含一些語句組成的一個語句塊。一個接收表達(dá)式可能只包含接收表達(dá)式自身(譯注:不把接收到的值賦值給變量什么的),就像上面的第一個case,或者包含在一個簡短的變量聲明中,像第二個case里一樣;第二種形式讓你能夠引用接收到的值。
select會等待case中有能夠執(zhí)行的case時去執(zhí)行。當(dāng)條件滿足時,select才會去通信并執(zhí)行case之后的語句;這時候其它通信是不會執(zhí)行的。一個沒有任何case的select語句寫作select{},會永遠(yuǎn)地等待下去。
示例:
package mainimport “time”import “fmt”// Go 的通道選擇器 讓你可以同時等待多個通道操作。// Go 協(xié)程和通道以及選擇器的結(jié)合是 Go 的一個強大特性。func main() {// 在我們的例子中,我們將從兩個通道中選擇。c1 := make(chan string)c2 := make(chan string)// 各個通道將在若干時間后接收一個值,這個用來模擬例如并行的 Go 協(xié)程中阻塞的 RPC 操作go func() {time.Sleep(time.Second * 1)c1 <- "one"}()go func() {time.Sleep(time.Second * 2)c2 <- "two"}()// 我們使用 select 關(guān)鍵字來同時等待這兩個值,并打印各自接收到的值。for i := 0; i < 2; i++ {select {case msg1 := <-c1:fmt.Println("received", msg1)case msg2 := <-c2:fmt.Println("received", msg2)}}// 我們首先接收到值 "one",然后就是預(yù)料中的 "two"了。// 注意從第一次和第二次 Sleeps 并發(fā)執(zhí)行,總共僅運行了兩秒左右。}
超時實現(xiàn)
package mainimport “time”import “fmt”// 超時 對于一個連接外部資源,或者其它一些需要花費執(zhí)行時間的操作的程序而言是很重要的。// 得益于通道和 select,在 Go中實現(xiàn)超時操作是簡潔而優(yōu)雅的。func main() {c1 := make(chan string, 1)// 在我們的例子中,假如我們執(zhí)行一個外部調(diào)用,并在 2 秒后通過通道 c1 返回它的執(zhí)行結(jié)果。go func() {time.Sleep(time.Second * 2)c1 <- "result 1"}()// 這里是使用 select 實現(xiàn)一個超時操作。res := <- c1 等待結(jié)果,<-Time.After 等待超時時間 1 秒后發(fā)送的值。// 由于 select 默認(rèn)處理第一個已準(zhǔn)備好的接收操作,如果這個操作超過了允許的 1 秒的話,將會執(zhí)行超時 case。select {case res := <-c1:fmt.Println(res)case <-time.After(time.Second * 1):fmt.Println("timeout 1")}// 如果我允許一個長一點的超時時間 3 秒,將會成功的從 c2接收到值,并且打印出結(jié)果。c2 := make(chan string, 1)go func() {time.Sleep(time.Second * 2)c2 <- "result 2"}()select {case res := <-c2:fmt.Println(res)case <-time.After(time.Second * 3):fmt.Println("timeout 2")}// 運行這個程序,首先顯示運行超時的操作,然后是成功接收的。// 使用這個 select 超時方式,需要使用通道傳遞結(jié)果。// 這對于一般情況是個好的方式,因為其他重要的 Go 特性是基于通道和select 的。}
非阻塞選擇器
package mainimport (“fmt”)// 常規(guī)的通過通道發(fā)送和接收數(shù)據(jù)是阻塞的。// 然而,我們可以使用帶一個 default 子句的 select 來實現(xiàn)非阻塞 的發(fā)送、接收,甚至是非阻塞的多路 select。func main() {messages := make(chan string)signals := make(chan bool)// 這里是一個非阻塞接收的例子。// 如果在 messages 中存在,然后 select 將這個值帶入 <-messages case中。// 如果不是,就直接到 default 分支中。select {case msg := <-messages:fmt.Println("received message", msg)default:fmt.Println("no message received")}// 一個非阻塞發(fā)送的實現(xiàn)方法和上面一樣。msg := "hi"select {case messages <- msg:fmt.Println("sent message", msg)default:fmt.Println("no message sent")}// 我們可以在 default 前使用多個 case 子句來實現(xiàn)一個多路的非阻塞的選擇器。// 這里我們試圖在 messages和 signals 上同時使用非阻塞的接受操作。select {case msg := <-messages:fmt.Println("received message", msg)case sig := <-signals:fmt.Println("received signal", sig)default:fmt.Println("no activity")}}
分享不易~ 感謝大家閱讀