GO語言的進階之路-goroutine(併發)

                     GO語言的進階之路-goroutine(併發)html

                                              做者:尹正傑程序員

版權聲明:原創做品,謝絕轉載!不然將追究法律責任。golang

 

 

  有人把Go比做21世紀的C 語言,第一是由於 Go語言設計簡單,第二,21世紀最重要的就是並行程序設計,而GO 從語言層面就支持了並行。Go語言中最重要的一個特性,那就是 go 關鍵字。優雅的併發編程範式,完善的併發支持,出色的併發性能是Go語言區別於其餘語言的一大特點。使用Go語言開發服務器程序時,就須要對它的併發機制有深刻的瞭解。編程

 

一.併發基礎數組

         回到在Windows和Linux出現以前的古老年代,程序員在開發程序時並無併發的概念,由於命令式程序設計語言是以串行爲基礎的,程序會順序執行每一條指令,整個程序只有一個執行上下文,即一個調用棧,一個堆。併發則意味着程序在運行時有多個執行上下文,對應着多個調用棧。咱們知道每個進程在運行時,都有本身的調用棧和堆,有一個完整的上下文,而操做系統在調度進程的時候,會保存被調度進程的上下文環境,等該進程得到時間片後,再恢復該進程的上下文到系統中。從整個操做系統層面來講,多個進程是能夠併發的,那麼併發的價值何在?下面咱們先看如下幾種場景。安全

  1>.一方面咱們須要靈敏響應的圖形用戶界面,一方面程序還須要執行大量的運算或者IO密集操做,而咱們須要讓界面響應與運算同時執行。服務器

  2>.當咱們的Web服務器面對大量用戶請求時,須要有更多的「Web服務器工做單元」來分別響應用戶。網絡

  3>.咱們的事務處於分佈式環境上,相同的工做單元在不一樣的計算機上處理着被分片的數據。多線程

  4>.計算機的CPU從單內核(core)向多內核發展,而咱們的程序都是串行的,計算機硬件的能力沒有獲得發揮。架構

  5>.咱們的程序由於IO操做被阻塞,整個程序處於停滯狀態,其餘IO無關的任務沒法執行。

       從以上幾個例子能夠看到,串行程序在不少場景下沒法知足咱們的要求。下面咱們概括了併發程序的幾條優勢,讓你們認識到併發勢在必行:

     a>.併發能更客觀地表現問題模型;

     b>.併發能夠充分利用CPU核心的優點,提升程序的執行效率;

     c>.併發能充分利用CPU與其餘硬件設備固有的異步性。

       如今咱們已經意識到併發的好處了,那麼到底有哪些方式能夠實現併發執行呢?就目前而言,併發包含如下幾種主流的實現模型。

  1>.多進程。多進程是在操做系統層面進行併發的基本模式。同時也是開銷最大的模式。在Linux平臺上,不少工具鏈正是採用這種模式在工做。好比某個Web服務器,它會有專門的進程負責網絡端口的監聽和連接管理,還會有專門的進程負責事務和運算。這種方法的好處在於簡單、進程間互不影響,壞處在於系統開銷大,由於全部的進程都是由內核管理的。

  2>.多線程。多線程在大部分操做系統上都屬於系統層面的併發模式,也是咱們使用最多的最有效的一種模式。目前,咱們所見的幾乎全部工具鏈都會使用這種模式。它比多進程 的開銷小不少,可是其開銷依舊比較大,且在高併發模式下,效率會有影響。

  3>.基於回調的非阻塞/異步IO。這種架構的誕生實際上來源於多線程模式的危機。在不少高併發服務器開發實踐中,使用多線程模式會很快耗盡服務器的內存和CPU資源。而這種模式經過事件驅動的方式使用異步IO,使服務器持續運轉,且儘量地少用線程,下降開銷,它目前在Node.js中獲得了很好的實踐。可是使用這種模式,編程比多線程要複雜,由於它把流程作了分割,對於問題自己的反應不夠天然。

  4>.協程。協程(Coroutine)本質上是一種用戶態線程,不須要操做系統來進行搶佔式調度,且在真正的實現中寄存於線程中,所以,系統開銷極小,能夠有效提升線程的任務併發性,而避免多線程的缺點。使用協程的優勢是編程簡單,結構清晰;缺點是須要語言的支持,若是不支持,則須要用戶在程序中自行實現調度器。目前,原生支持協程的語言還不多。

      接下來咱們先詮釋一下傳統併發模型的缺陷,以後再講解goroutine併發模型是如何逐一解決這些缺陷的。

      人的思惟模式能夠認爲是串行的,並且串行的事務具備肯定性。線程類併發模式在原先的肯定性中引入了不肯定性,這種不肯定性給程序的行爲帶來了意外和危害,也讓程序變得不可控。線程之間通訊只能採用共享內存的方式。爲了保證共享內存的有效性,咱們採起了不少措施,好比加鎖等,來避免死鎖或資源競爭。實踐證實,咱們很難面面俱到,每每會在工程中遇到各類奇怪的故障和問題。

     咱們能夠將以前的線程加共享內存的方式概括爲「共享內存系統」,雖然共享內存系統是一種有效的併發模式,但它也暴露了衆多使用上的問題。計算機科學家們在近40年的研究中又產生了一種新的系統模型,稱爲「消息傳遞系統」。

     對線程間共享狀態的各類操做都被封裝在線程之間傳遞的消息中,這一般要求:發送消息時對狀態進行復制,而且在消息傳遞的邊界上交出這個狀態的全部權。從邏輯上來看,這個操做與共享內存系統中執行的原子更新操做相同,但從物理上來看則很是不一樣。因爲須要執行復制操做,因此大多數消息傳遞的實如今性能上並不優越,但線程中的狀態管理工做一般會變得更爲簡單。

     最先被普遍應用的消息傳遞系統是由C. A. R. Hoare在他的Communicating Sequential Processes中提出的。在CSP系統中,全部的併發操做都是經過獨立線程以異步運行的方式來實現的。這些線程必須經過在彼此之間發送消息,從而向另外一個線程請求信息或者將信息提供給另外一個線程。使用相似CSP的系統將提升編程的抽象級別。

     隨着時間的推移,一些語言開始完善消息傳遞系統,並以此爲核心支持併發,好比Erlang。

 

二.協程

 再說協成以前,咱們須要瞭解兩個概念,即用戶態和內核態。

1.什麼是用戶態;

  官方解釋:用戶態(user mode)在計算機結構指兩項相似的概念。在CPU的設計中,用戶態指非特權狀態。在此狀態下,執行的代碼被硬件限定,不能進行某些操做,好比寫入其餘進程的存儲空間,以防止給操做系統帶來安全隱患。在操做系統的設計中,用戶態也相似,指非特權的執行狀態。內核禁止此狀態下的代碼進行潛在危險的操做,好比寫入系統配置文件、殺掉其餘用戶的進程、重啓系統等。

   應用程序在用戶態下運行,僅僅只能執行cpu整個指令集的一個子集,該子集中不包含操做硬件功能的部分,所以,通常狀況下,在用戶態中有關I/O和內存保護(操做系統佔用的內存是受保護的,不能被別的程序佔用)。

  若是感興趣的朋友能夠參考:https://baike.baidu.com/item/%E7%94%A8%E6%88%B7%E6%80%81/9548791?fr=aladdin

 

2.什麼是內核態;

  內核態也叫和核心態。

  官方解釋:在處理器的存儲保護中,主要有兩種權限狀態,一種是核心態(管態),也被稱爲特權態;一種是用戶態(目態)。核心態是操做系統內核所運行的模式,運行在該模式的代碼,能夠無限制地對系統存儲、外部設備進行訪問。

  操做系統在內核態運行狀況下能夠訪問硬件上全部的內容。

  若是感興趣的朋友能夠參考:https://baike.baidu.com/item/%E6%A0%B8%E5%BF%83%E6%80%81/6845908?fr=aladdin

 

3.什麼是協程;

  官方解釋:一個程序能夠包含多個協程,能夠對比與一個進程包含多個線程,於是下面咱們來比較協程和線程。咱們知道多個線程相對獨立,有本身的上下文,切換受系統控制;而協程也相對獨立,有本身的上下文,可是其切換由本身控制,由當前協程切換到其餘協程由當前協程來控制。

  執行體是個抽象的概念,在操做系統層面有多個概念與之對應,好比操做系統本身掌管的進程(process)、進程內的線程(thread)以及進程內的協程(coroutine,也叫輕量級線程)。與傳統的系統級線程和進程相比,協程的最大優點在於其「輕量級」,能夠輕鬆建立上百萬個而不會致使系統資源衰竭,而線程和進程一般最多也不能超過1萬個。這也是協程也叫輕量級線程的緣由。

  多數語言在語法層面並不直接支持協程,而是經過庫的方式支持,但用庫的方式支持的功能也並不完整,好比僅僅提供輕量級線程的建立、銷燬與切換等能力。若是在這樣的輕量級線程中調用一個同步 IO 操做,好比網絡通訊、本地文件讀寫,都會阻塞其餘的併發執行輕量級線程,從而沒法真正達到輕量級線程自己指望達到的目標。

  Go 語言在語言級別支持輕量級線程,叫goroutine。Go 語言標準庫提供的全部系統調用操做(固然也包括全部同步 IO 操做),都會出讓 CPU 給其餘goroutine。這讓事情變得很是簡單,讓輕量級線程的切換管理不依賴於系統的線程和進程,也不依賴於CPU的核心數量。協程(coroutine)是Go語言中的輕量級線程實現,由Go運行時(runtime)管理。在一個函數調用前加上go關鍵字,此次調用就會在一個新的goroutine中併發執行。當被調用的函數返回時,這個goroutine也自動結束。須要注意的是,若是這個函數有返回值,那麼這個返回值會被丟棄。協成工做在用戶態,它相似於現場的運行方式能夠並行處理任務。

 

三.goroutine

  goroutine不一樣於thread,threads是操做系統中的對於一個獨立運行實例的描述,不一樣操做系統,對於thread的實現也不盡相同;可是,操做系統並不知道goroutine的存在,goroutine的調度是有Golang運行時進行管理的。啓動thread雖然比process所需的資源要少,可是多個thread之間的上下文切換仍然是須要大量的工做的(寄存器/Program Count/Stack Pointer/...),Golang有本身的調度器,許多goroutine的數據都是共享的,所以goroutine之間的切換會快不少,啓動goroutine所耗費的資源也不多,一個Golang程序同時存在幾百個goroutine是很正常的。goroutine是Go語言中的輕量級線程實現,由Go運行時(runtime)管理.goroutine 比thread 更易用、更高效、更輕便。

1.建立一個goroutine 

   goroutine 是經過 Go 的 runtime管理的一個線程管理器。經過關鍵字go 就啓動了一個 goroutine。咱們來看一個例子:

 1 /*
 2 #!/usr/bin/env gorun
 3 @author :yinzhengjie
 4 Blog:http://www.cnblogs.com/yinzhengjie/tag/GO%E8%AF%AD%E8%A8%80%E7%9A%84%E8%BF%9B%E9%98%B6%E4%B9%8B%E8%B7%AF/
 5 EMAIL:y1053419035@qq.com
 6 */
 7 
 8 package main
 9 
10 import (
11     "time"
12     "fmt"
13 )
14 
15 func MyEcho(s string)  {
16     for i := 0; i < 5; i++ {
17         time.Sleep(100*time.Millisecond)  //表示每次循環後都要休息100毫秒。
18         fmt.Println(s)
19     }
20 }
21 
22 func main() {
23     go MyEcho("尹正傑")  //在函數執行前加個go,表示單獨起了一個協程,表示和當前主協程(main)並駕齊驅運行代碼。
24     MyEcho("Golang")
25 }
26 
27 
28 
29 
30 
31 #以上代碼執行結果以下:(須要注意的是,他們輸出的順序是不肯定的喲~)
32 Golang
33 尹正傑
34 Golang
35 尹正傑
36 尹正傑
37 Golang
38 Golang
39 尹正傑
40 尹正傑
41 Golang

 

2.goroutine的侷限性

   Go程序從初始化 main package 並執行 main() 函數開始,當 main() 函數返回時,程序退出,且程序並不等待其餘goroutine(非主goroutine)結束。光這樣說你們可能不是很理解,接下來咱們就用實際代碼來講明,下面的一段代碼使對切片「yzj」的元素進行排序,而主程序運行時間是12秒,咱們能夠清楚的看到「yzj」這個切片的長度是13,這意味着須要開啓13個goroutine,而咱們定義的主函數的運行的時間是12秒。這意味着12秒以後,無論有多少個goroutine在運行程序都會自動結束。這也是爲何咱們沒有看到「yzj」這個切片數字的另外兩個元素輸出,即都是int型的15和17。由於這2個goroutine執行完畢的時間是15秒和17秒,而程序的最長容許的運行時間是12秒。

 1 /*
 2 #!/usr/bin/env gorun
 3 @author :yinzhengjie
 4 Blog:http://www.cnblogs.com/yinzhengjie/tag/GO%E8%AF%AD%E8%A8%80%E7%9A%84%E8%BF%9B%E9%98%B6%E4%B9%8B%E8%B7%AF/
 5 EMAIL:y1053419035@qq.com
 6 */
 7 
 8 package main
 9 
10 import (
11     "time"
12     "fmt"
13 )
14 
15 func main() {
16     yzj := []int{2,7,1,6,4,3,11,15,17,5,8,9,12}
17     fmt.Println("該切片的長度是:",len(yzj))
18     for _,n := range yzj{
19         go func(n int) { //定義一個匿名函數,並對該函數開啓協程,每次循環都會開啓一個協成,也就是說它開啓了13個協程。
20             time.Sleep(time.Duration(n) * time.Second) //表示每循環一次就須要睡1s,睡的總時間是由n來控制的,總長度是由s切片數組中最大的一個數字決定,也就是說這個協成最少須要17秒纔會結束喲。
21             fmt.Println(n)
22         }(n)  //因爲這個函數是匿名函數,因此調用方式就直接:(n)調用,不用輸入函數名。
23     }
24     time.Sleep(12*time.Second) //主進程要執行的時間是12秒.
25 }
26 
27 
28 
29 #以上代碼執行結果以下:
30 該切片的長度是: 13
31 1
32 2
33 3
34 4
35 5
36 6
37 7
38 8
39 9
40 11
41 12

  經過上面這段代碼咱們能夠明顯的知道這個程序是有bug的,由於咱們的要求是對切片「yzj」順序的從小到大的排序。可是「yzj」這個切片中的元素「15」和「17」是沒有輸出出來的。固然從上面的分析你會立馬找出解決方案,好比說將主程序的運行時間從12秒改爲大於或等於17秒不就得了。good,這種改法的確是能夠針對這個程序是有效的。可是你沒有發現這個效率很低嗎?那麼咱們是否是有一種機制可讓goroutine和main()進行通訊呢?要讓主函數等待全部goroutine退出後再返回,如何知道goroutine都退出了呢?這就引出了多個goroutine之間通訊的問題。 

         實現一個如此簡單的功能,卻寫出如此臃腫並且難以理解的代碼。想象一下,在一個大的系統中具備無數的鎖、無數的共享變量、無數的業務邏輯與錯誤處理分支,那將是一場噩夢。這噩夢就是衆多C/C++開發者正在經歷的,其實Java和C#開發者也好不到哪裏去。

 

         Go語言既然以併發編程做爲語言的最核心優點,固然不至於將這樣的問題用這麼無奈的方式來解決。Go語言提供的是另外一種通訊模型,即以消息機制而非共享內存做爲通訊方式。消息機制認爲每一個併發單元是自包含的、獨立的個體,而且都有本身的變量,但在不一樣併發單元間這些變量不共享。每一個併發單元的輸入和輸出只有一種,那就是消息。這有點相似於進程的概念,每一個進程不會被其餘進程打擾,它只作好本身的工做就能夠了。不一樣進程間靠消息來通訊,它們不會共享內存。

 

         Go語言提供的消息通訊機制被稱爲channel,接下來咱們將詳細介紹channel。如今,讓咱們用Go語言社區的那句著名的口號來結束這一小節:「不要經過共享內存來通訊,而應該經過通訊來共享內存。」不過想要了解golang關於鎖的通訊機制的小夥伴們,我也將筆記早就總結出來了(使勁戳我就成)。

 

四.channel

  channel是Go語言在語言級別提供的goroutine間的通訊方式。咱們可使用channel在兩個或多個goroutine之間傳遞消息。channel是進程內的通訊方式,所以經過channel傳遞對象的過程和調用函數時的參數傳遞行爲比較一致,好比也能夠傳遞指針等。若是須要跨進程通訊,咱們建議用分佈式系統的方法來解決,好比使用Socket或者HTTP等通訊協議。Go語言對於網絡方面也有很是完善的支持。

  channel是類型相關的。也就是說,一個channel只能傳遞一種類型的值,這個類型須要在聲明channel時指定。若是對Unix管道有所瞭解的話,就不難理解channel,能夠將其認爲是一種類型安全的管道。

  在瞭解channel的語法前,咱們先看下用channel的方式重寫上面的例子是什麼樣子的,以此對channel先有一個直感的認識。

 

 

須要從新上面案例。暫時空出來,等我把channel講解完畢在寫

 

1.基本語法(channels)

 1 /*
 2 #!/usr/bin/env gorun
 3 @author :yinzhengjie
 4 Blog:http://www.cnblogs.com/yinzhengjie/tag/GO%E8%AF%AD%E8%A8%80%E7%9A%84%E8%BF%9B%E9%98%B6%E4%B9%8B%E8%B7%AF/
 5 EMAIL:y1053419035@qq.com
 6 */
 7 
 8 package main
 9 
10 import "fmt"
11 
12 func main() {
13     var  yzj_string chan string    //般channel的聲明形式爲:var chanName chan ElementType.與通常的變量聲明不一樣的地方僅僅是在類型以前加了 chan 關鍵字。 ElementType 指定這個 channel所能傳遞的元素類型。
14 
15     var  yzj_map  map[string]chan bool //這是咱們聲明一個的map ,元素是 bool 型的channel。
16 
17     yzj_channel := make(chan []map[string]int)//定義一個channel也很簡單,直接使用內置的函數 make() 便可。
18 
19     fmt.Println(yzj_string)
20     fmt.Println(yzj_map)
21     fmt.Println(yzj_channel)
22 /*
23     writ_channel := "yinzhengjie"
24 
25     yzj_string <- writ_channel        //在channel的用法中,最多見的包括寫入和讀出。將一個數據寫入(發送)至channel的語法很直觀。向channel寫入數據一般會致使程序阻塞,直到有其餘goroutine從這個channel中讀取數據。
26 
27     read_channel := yzj_string        //若是channel以前沒有寫入數據,那麼從channel中讀取數據也會致使程序阻塞,直到channel中被寫入數據爲止。咱們以後還會提到如何控制channel只接受寫或者只容許讀取,即單向channel。
28 */
29 }
30 
31 
32 
33 #以上代碼執行結果以下:
34 <nil>
35 map[]
36 0xc04203a060

   知道如何定義一個channel以後,咱們也能夠作一下簡單的應用,代碼以下:

 1 /*
 2 #!/usr/bin/env gorun
 3 @author :yinzhengjie
 4 Blog:http://www.cnblogs.com/yinzhengjie/tag/GO%E8%AF%AD%E8%A8%80%E7%9A%84%E8%BF%9B%E9%98%B6%E4%B9%8B%E8%B7%AF/
 5 EMAIL:y1053419035@qq.com
 6 */
 7 
 8 package main
 9 
10 import "fmt"
11 
12 func MySum(a []int, sum chan int) {    //該函數是對切片數組求和,須要傳入一個切片數組和一個channel。
13     value := 0
14     for _, v := range a {
15         value += v
16     }
17     sum <- value             //將數據發送到channel中去。
18 }
19 func main() {
20     yzj := []int{1,2,3,4,5,-10}
21     sum := make(chan int)                 //用chan定義一個channel對象名稱爲「sum」,其類型是「int」。
22     go MySum(yzj[:len(yzj)/2], sum)        //將切片的前一半發送給channel對象「sum」
23     go MySum(yzj[len(yzj)/2:], sum)        //將切片的後一半發送給channel對象「sum」
24     x, y := <-sum, <-sum                 //從咱們定義中的channel中獲取數據,並將讀取到的value賦值給x,y
25     fmt.Println("X    =" ,x)
26     fmt.Println("Y    =",y)
27     fmt.Println("X+Y  =" ,x+y)
28 }
29 
30 
31 
32 #以上代碼執行結果以下:
33 X    = 6
34 Y    = -1
35 X+Y  = 5

 

2.緩衝機制(Buffered Channels)

   以前咱們示範建立的都是不帶緩衝的channel,這種作法對於傳遞單個數據的場景能夠接受,但對於須要持續傳輸大量數據的場景就有些不合適了。接下來咱們介紹如何給channel帶上緩衝,從而達到消息隊列的效果。要建立一個帶緩衝的channel,其實也很是容易,好比「yzj := make(chan int ,4096)」在調用 make() 時將緩衝區大小做爲第二個參數傳入便可,建立了一個大小爲4096的 int 類型 channel ,即便沒有讀取方,寫入方也能夠一直往channel裏寫入,在緩衝區被填完以前都不會阻塞。

 1 /*
 2 #!/usr/bin/env gorun
 3 @author :yinzhengjie
 4 Blog:http://www.cnblogs.com/yinzhengjie/tag/GO%E8%AF%AD%E8%A8%80%E7%9A%84%E8%BF%9B%E9%98%B6%E4%B9%8B%E8%B7%AF/
 5 EMAIL:y1053419035@qq.com
 6 */
 7 
 8 package main
 9 import (
10     "fmt"
11 )
12 
13 func FibonacciSequence(num int, Producer chan int) {
14     x, y := 1, 1
15     for i := 0; i < num; i++ {
16         Producer <- x
17         x, y = y, x + y
18     }
19     close(Producer)    //能夠顯式的關閉channel,生產者經過關鍵字 close 函數關閉 channel。關閉channel 以後就沒法再接受或發送任何數據了。 記住應該在生產者的地方關閉channel,
20     // 而不是消費的地方去關閉它,這樣容易引發 panic 另外記住一點的就是channel 不像文件之類的,不須要常常去關閉,只有當你確實沒有任何發送數據了,或者你想顯式的結束 range 循環之類的。
21     
22 }
23 
24 func main() {
25     yzj := make(chan int, 5)
26     go FibonacciSequence(cap(yzj), yzj)
27 
28     value, status := <-yzj        //注意,這裏的「value」至關對「yzj」這個channel進行讀取一次數據喲。「status」的值如何爲「true」則代表channel尚未被關閉喲。
29     fmt.Println(value,status)
30 
31     for i := range yzj {    //咱們使用range語法可以不斷的讀取channel 裏面的數據,直到該 channel 被顯式的關閉。
32         fmt.Println(i)
33     }
34 
35     value, status = <-yzj    //注意,「status」的值如何爲「false」,那麼說明 channel 已經沒有任何數據而且已經被關閉。
36     fmt.Println(value,status)
37 }
38 
39 
40 
41 
42 #以上代碼執行結果以下:
43 1 true
44 1
45 2
46 3
47 5
48 0 false

 

3.channel的選擇語句selecte語法

  早在Unix時代, select 機制就已經被引入。經過調用 select() 函數來監控一系列的文件句柄,一旦其中一個文件句柄發生了IO動做,該 select() 調用就會被返回。後來該機制也被用於實現高併發的Socket服務器程序。Go語言直接在語言級別支持 select 關鍵字,用於處理異步IO問題。select 的用法與 switch 語言很是相似,由 select 開始一個新的選擇塊,每一個選擇條件由case 語句來描述。與 switch 語句能夠選擇任何可以使用相等比較的條件相比, select 有比較多的限制,其中最大的一條限制就是每一個 case 語句裏必須是一個IO操做。

  咱們上面介紹的都是隻有一個channel 的狀況,那麼若是存在多個 channel 的時候,咱們該如何操做呢,Go裏面提供了一個關鍵字 select ,經過 select 能夠監聽channel 上的數據流動。select 默認是阻塞的,只有當監聽的channel 中有發送或接收能夠進行時纔會運行,當多個channel 都準備好的時候,select 是隨機的選擇一個執行的。

 1 /*
 2 #!/usr/bin/env gorun
 3 @author :yinzhengjie
 4 Blog:http://www.cnblogs.com/yinzhengjie/tag/GO%E8%AF%AD%E8%A8%80%E7%9A%84%E8%BF%9B%E9%98%B6%E4%B9%8B%E8%B7%AF/
 5 EMAIL:y1053419035@qq.com
 6 */
 7 
 8 package main
 9 
10 import "fmt"
11 
12 func fibonacci(channel_name,quit chan int)  { //定義兩個channle對象channel_name和quit。
13     x,y := 0,1
14     for{
15         select {
16         case channel_name <- x:  //用channel_name接受數據。
17             x,y = y,x+y
18 
19         case <-quit:  //表示當接收到quit的channel時,就執行如下代碼。其實就是實現關閉channel的功能。可是它並無權限主動關閉channel,而是負責監聽channel 上的數據流動。
20             fmt.Println("EXIT")
21             return //函數一退出協程也就跟着退出了
22         }
23     }
24 }
25 
26 func main() {
27     channel_name := make(chan int)
28     quit := make(chan int)
29 
30     go func() {    //運行一個匿名函數。
31         for i := 0; i < 11; i++ {
32             fmt.Println(<-channel_name)  //"<-channel_name"表示讀取channel_name中的參數。
33         }
34         quit<- 100     //當for循環結束後,咱們隨便給quit的channel傳一個值就能夠實現退出函數的功能,咱們以前須要用close(c)來退出發信號的功能,主動權在"fibonacci",而咱們如今咱們用quit來主動退出協程。
35     }()
36 
37     fibonacci(channel_name,quit)  //將channel_name和quit傳遞給fibonacci函數
38 }
39 
40 
41 
42 
43 #以上代碼執行結果以下:
44 0
45 1
46 1
47 2
48 3
49 5
50 8
51 13
52 21
53 34
54 55
55 EXIT

 

4.channel的默認語句default語法

 1 /*
 2 #!/usr/bin/env gorun
 3 @author :yinzhengjie
 4 Blog:http://www.cnblogs.com/yinzhengjie/tag/GO%E8%AF%AD%E8%A8%80%E7%9A%84%E8%BF%9B%E9%98%B6%E4%B9%8B%E8%B7%AF/
 5 EMAIL:y1053419035@qq.com
 6 */
 7 
 8 package main
 9 
10 import (
11     "time"
12     "fmt"
13 )
14 
15 func main() {
16     tick := time.Tick(1000*time.Millisecond) //也能夠這樣寫:「tick := time.NewTicker(1000*time.Millisecond).C」其中這個點C就是一個channel。
17     boom := time.After(5000*time.Millisecond)
18     for  {
19         select {
20         case <-tick:
21             fmt.Println("滴答。。。")
22         case <-boom:
23             fmt.Println("砰~")
24             return
25         default:
26             fmt.Println("吃一口涼皮")
27             time.Sleep(500*time.Millisecond)
28         }
29     }
30 }
31 
32 
33 
34 
35 #以上代碼執行結果以下:
36 吃一口涼皮
37 吃一口涼皮
38 滴答。。。
39 吃一口涼皮
40 吃一口涼皮
41 滴答。。。
42 吃一口涼皮
43 吃一口涼皮
44 滴答。。。
45 吃一口涼皮
46 吃一口涼皮
47 滴答。。。
48 吃一口涼皮
49 吃一口涼皮
50 滴答。。。
51 砰~

 

5.超時機制(timeout)

  Go語言沒有提供直接的超時處理機制,但咱們能夠利用 select 機制。雖然 select 機制不是專爲超時而設計的,卻能很方便地解決超時問題。由於 select 的特色是隻要其中一個 case 已經完成,程序就會繼續往下執行,而不會考慮其餘 case 的狀況。有時候會出現goroutine 阻塞的狀況,那麼咱們如何避免整個的程序進入阻塞的狀況呢?咱們能夠利用select 來設置超時,經過以下的方式實現:

 1 /*
 2 #!/usr/bin/env gorun
 3 @author :yinzhengjie
 4 Blog:http://www.cnblogs.com/yinzhengjie/tag/GO%E8%AF%AD%E8%A8%80%E7%9A%84%E8%BF%9B%E9%98%B6%E4%B9%8B%E8%B7%AF/
 5 EMAIL:y1053419035@qq.com
 6 */
 7 
 8 package main
 9 
10 import (
11     "time"
12     "fmt"
13 )
14 
15 func main() {
16     TimeOut := time. After(5 * time.Second)    //定義超時時間。
17     NerverRings := make(chan int)
18     RingsOccasionally := make(chan bool)
19     go func() {
20         for {
21             select {
22             case value := <- NerverRings:
23                 fmt.Println(value)
24             case <- TimeOut:
25                 println("對不起,到目前爲止,NerverRings並無接收到任何數據!程序已經終止。")
26                 RingsOccasionally <- true
27                 break
28             }
29         }
30     }()
31 
32     <- RingsOccasionally  //從RingsOccasionally這個channel中獲取數據,因此在獲取數據以前,成功程序是出於阻塞狀態的喲。
33     
34 }
35 
36 
37 
38 #以上代碼執行結果以下:
39 對不起,到目前爲止,NerverRings並無接收到任何數據!程序已經終止。

 

 6.channel的傳遞

  須要注意的是,在Go語言中channel自己也是一個原生類型,與 map 之類的類型地位同樣,所以channel自己在定義後也能夠經過channel來傳遞。具體案例以下:

 1 /*
 2 #!/usr/bin/env gorun
 3 @author :yinzhengjie
 4 Blog:http://www.cnblogs.com/yinzhengjie/tag/GO%E8%AF%AD%E8%A8%80%E7%9A%84%E8%BF%9B%E9%98%B6%E4%B9%8B%E8%B7%AF/
 5 EMAIL:y1053419035@qq.com
 6 */
 7 
 8 package main
 9 
10 import (
11     "fmt"
12     "time"
13 )
14 
15 type PipeData struct {        //定義結構體 PipeData
16     value int
17     handler func(int) int
18     next chan int
19 }
20 
21 func Handle(queue chan *PipeData) {
22     for data := range queue{
23         fmt.Println("value=",data.value)
24         fmt.Println("handler=",data.handler)
25         fmt.Println("next=",data.next)
26         data.next <- data.handler(data.value)
27     }
28 }
29 
30 func main() {
31     yzj := make(chan *PipeData)        //因爲Handle支持傳入指針類型的*PipeData,所以咱們初始化的時候要個其類型保持一致。
32 
33     go func() {    //咱們開啓一個goroutine,讓其不斷的發送數據。
34         data := &PipeData{value:100,handler: func(i int) int {
35             return i
36         }}    //咱們須要將數據定義好,這個data就是咱們須要發送的數據。
37         yzj <- data    //將數據發送給名爲yzj的channel。
38     }()
39 
40     go Handle(yzj)    //當咱們把數據傳給channel變量yzj以後,就能夠把這個channel繼續傳給Handle這個函數啦。
41     //
42     //data := <- yzj //接下來咱們開始從channel讀取數據。
43     //fmt.Println(data.value)
44     //fmt.Println(data.handler)
45     //fmt.Println(data.next)
46     time.Sleep(time.Second * 3)    //爲了不主進程提早結束,咱們須要讓主進程拖長一點時間,之後我會介紹更簡單的方法來控制這個時間。
47 }
48 
49 
50 
51 
52 #以上代碼執行結果以下:
53 value= 100
54 handler= 0x489250
55 next= <nil>

 

7.單向channel

   顧名思義,單向channel只能用於發送或者接收數據。channel自己必然是同時支持讀寫的,不然根本無法用。假如一個channel真的只能讀,那麼確定只會是空的,由於你沒機會往裏面寫數據。同理,若是一個channel只容許寫,即便寫進去了,也沒有絲毫意義,由於沒有機會讀取裏面的數據。所謂的單向channel概念,其實只是對channel的一種使用限制。

  定義一個單向channel很簡單:

 

 1 /*
 2 #!/usr/bin/env gorun
 3 @author :yinzhengjie
 4 Blog:http://www.cnblogs.com/yinzhengjie/tag/GO%E8%AF%AD%E8%A8%80%E7%9A%84%E8%BF%9B%E9%98%B6%E4%B9%8B%E8%B7%AF/
 5 EMAIL:y1053419035@qq.com
 6 */
 7 
 8 package main
 9 
10 import "fmt"
11 
12 func main() {
13     data := make(chan int)    //默認該channel就是可讀可寫的喲。
14 
15     go func() {
16         write := chan<- int(data)    //此處的write 是一個單向的寫入channel。
17         write <- 100
18         fmt.Println(write)
19     }()
20 
21     read := <-chan int(data)    //此處的read就是一個單向的讀取channel。
22     fmt.Println(read)
23 }
24 
25 
26 
27 #以上代碼執行結果以下:
28 0xc04203a060
29 100

 

  固然,咱們能夠在函數中對channel進行只讀或是隻寫的操做,以下:

 1 /*
 2 #!/usr/bin/env gorun
 3 @author :yinzhengjie
 4 Blog:http://www.cnblogs.com/yinzhengjie/tag/GO%E8%AF%AD%E8%A8%80%E7%9A%84%E8%BF%9B%E9%98%B6%E4%B9%8B%E8%B7%AF/
 5 EMAIL:y1053419035@qq.com
 6 */
 7 
 8 package main
 9 
10 import (
11     "fmt"
12     "time"
13 )
14 
15 func Parse(ch <-chan int) {    //Parse函數的功能是隻讀的channel。注意的是,channel自己就是可讀可寫的,所謂的只讀channel和只寫channel只是使用者在用法上的限制而已。
16     for value := range ch {
17         fmt.Println("Parsing value", value)
18     }
19 }
20 
21 func main() {
22     data := make(chan int)
23     go Parse(data)    //注意,這行代碼是阻塞代碼。咱們知道這行代碼使從channel中讀取數據,可是目前還沒往channel發送數據。咱們用go關鍵字開啓一個協程,可讓代碼繼續往下執行。
24     data <- 10000    //往channel發送的數據。
25     time.Sleep(time.Second * 1)    //讓主程序運行一秒鐘,避免主進程提早比goroutine結束。
26 
27 }
28 
29 
30 
31 #以上代碼執行結果以下:
32 Parsing value 10000
相關文章
相關標籤/搜索