Java程序員快速入門 go 語言

jopen 11年前發布 | 96K 次閱讀 Go語言 Google Go/Golang開發

這篇文章幫助Java程序員快速入門Go語言。

本文將以一個有代表性的例子為開始,以此讓Java程序員對Go語言有個初步認識,隨后將詳細的描述Go語言的各個構成模塊,最后以一個例子來講解Go語言與Java語言的不同點。

先來認識堆棧(例子)

為了滿足大家的好奇心,我們將開始于一個麻雀雖小但五臟內全的例子,這個例子將是Stack.java的Go語言版本。

//實現堆棧的包集合
package collection

//生成空堆棧 type Stack struct { data []interface{} }

//壓棧頂 func (s *Stack) Push(x interface{}) { s.data = append(s.data, x) }

//彈棧頂,并刪除棧頂 //如果是空棧將會引發運行時錯誤 func (s *Stack) Pop() interface{} { i := len(s.data) - 1 res := s.data[i] s.data[i] = nil // to avoid memory leak s.data = s.data[:i] return res }

//返回棧大小 func (s *Stack) Size() int { return len(s.data) }</pre>

  • 用純文本在聲明語句上方添加注釋。
  • 申明的名字寫在類型的后面。
  • 結構體和Java中類相對應,但結構體的成員只能是變量,不能是方法。
  • interface{}類型對應Java中的Object。它不僅能代表引用類型,同時也能實現Go語言的所有類型。
  • (s *Stack)表示申明一個方法,s對應于Java中方法隱式傳的this參數。
  • 操作符:=表示申明和初始化一個變量,其類型由初始化語句決定。
  • </ul>

    下面是是使用collection.Stackabstract數據類型的簡單例子。

    package collection_test

    import ( collection "." "fmt" )

    func Example() { var s collection.Stack s.Push("world") s.Push("hello, ") for s.Size() > 0 { fmt.Print(s.Pop()) } fmt.Println() // Output: hello, world }</pre>將測試包package colloection_test和collectionpackage放在同一目錄。第一個import聲明“.”表示將要用當前路徑中的包并同時命名為collection,“fmt”是標準庫的包名,沒有重新命名,所以就直接用包名fmt即可。

    概念上的不同點

    • Go沒有類的構造器。Go提供結構體接口,類的繼承以及動態方法查詢來進行實例化,而非通過實例方法。接口同樣用于Java中的泛型。
    • Go中不僅對象和數組可以有指向數據的指針,其它所有類型都有。對于任何類型T,都有一個指針類型*T與之相對應,其表明指向類型為T的值。
    • Go允許在任何類型上創建方法;方法的接收者,就是Java中this,可以是個值或是指針。
    • 數組在Go中是一些值。當數組作為函數參數傳遞時,函數將收到數組值的拷貝,不是指針。然而實際中,經常用slices作為參數;因為切片中有數組的引用。
    • Go中提供字串類型;字串就像是由比特序列構成的slices,但字串是不可變的。
    • 在Go中哈希表叫maps。
    • Go提過channels類型來在多線程,goroutines之間通信來實現并發。
    • 一般類型(maps,slices,channels)傳參是引用,不是值。如果傳參map,參數不是對map的值拷貝;如果函數里改變了map的值,函數調用者的map也將改變。這個和Java中map類似。
    • Go中訪問權限有兩種,和Java中public 和包private類似。如果聲明的名稱首字母大寫就是public,否則就是包private。
    • Go用error類型取代Java中的異常,諸如訪問文件到結尾、運行時刻panic,數組越界等等。
    • Go不支持隱式類型轉換,必須是顯式的。
    • Go不支持覆蓋。函數名和方法名在同一作用域必須不同。
    • Go用nil表示空指針,而Java用null。
    • </ul>

      語法

      Java程序員快速入門 go 語言

      聲明

      聲明的語法同Java比較是逆向的。你寫上名字然后再跟著寫類型。從左至右的類型聲明也許讀起來容易些。

      </tr> </tbody> </table>

      Go                            約相等的Java寫法

      </tr>

      </tr>

      </tr>

      </tr>

      </tr>

      </tr>

      </tr>

      </tr> </tbody> </table>

      聲明一般是采用一個關鍵字后面跟著被定義對象的名稱,這種形式。關鍵字是const、type、var或者func其中之一。你也可以使用一個關鍵字后面跟著放在括號之中的一系列聲明,這種形式。

      var (
          n int
          x float64
      )
      當聲明函數時,你必須為每個參數提供一個名稱,或者不為任何參數提供名稱;你不能省略一些參數的名稱并提供其它參數的名稱。你可以用相同的類型來組織幾個名字,例如:
      func f(i, j, k int, s, t string)
      一個變量可以在聲明它時初始化值。進行此操作時,可以指定變量的類型,但這并非必需的。當不指定類型時,則默認為初始化表達式的類型。
      var v9 = *v2

      如果一個變量沒有明確地初始化,則必須指定其類型。在這種情況下,它將隱式地初始化為其類型的零值(0,nil 等)。在Go語言中沒有其它某些語言中的未初始化的變量。

      短聲明

      在函數內,可以用 := 進行短聲明,比如:

      v10 := v1
      它等效于:
      var v10 = v1

      函數類型

      在Go語言中,函數是一等公民。Go的函數類型表示有相同的參數和結果類型的所有函數的集合。

      type binOp func(int, int) int

      var op binOp add := func(i, j int) int { return i + j }

      op = add n = op(100, 200) // n = 100 + 200</pre>

      多重賦值

      Go 允許多重賦值。右邊的表達式先被求值然后賦給左邊的操作數。

      i, j = j, i  // 交換 i 和 j 的值(不用象傳統語言中需要第三個臨時變量).

      函數可以有多個返回值,用參數括號后面的一個括號中的列表表示。返回值通過賦予一個變量列表來存儲,如:

      func f() (i int, pj *int) { ... }
      v1, v2 = f()

      空標識符

      空標識符用下劃線字符表示,它提供了一種方法來忽略由多值表達式返回的某個值,如:

      v1, _ = f()  // 忽略f()返回的第二個值.

      分號和格式

      不需要擔心分號和格式,你可以用 gofmt程序創建一個標準的Go樣式。雖然這個樣式最初看起來或許有點古怪,但它同任何其它樣式一樣的好,而且熟悉以后會感覺越來越舒服。

      在實踐中Go代碼使用分號不多。嚴格說,所有的Go語句都是由分號終止。不過,Go在非空白行的末尾隱式插入一個分號,除非該行明顯未結束。這帶來的影響是,在某些情況下Go不允許換行符。例如,你不能這么寫

      func g()
      {            // INVALID; "{" should be on previous line.
      }

      g()后面會插入一個分號,使它成為一個函數聲明,而不是函數定義。類似的,你也不能寫

      if n == 0 {
      }
      else {       // INVALID; "else {" should be on previous line.
      }

       }后面else前面會插入一個分號,導致語法錯誤。

      條件語句

      Go 不使用括號來包裹 if 語句中的條件,和 for 語句中的表達式, 以及 switch 語句中的值。但另一方面,它必須用花括號來包裹 if 或 for 的執行語句體。

      if a < b { f() }
      if (a < b) { f() }           // 不需要括號.
      if (a < b) f()               // 非法(執行體未用花括號括起來)
      for i = 0; i < 10; i++ {}
      for (i = 0; i < 10; i++) {}  // 非法(表達式不需要括起來)
      此外,if 和 switch 接受一個可選的初始化語句,通常用它建立一個局部變量,如:
      if err := file.Chmod(0664); err != nil {
          log.Print(err)
          return err
      }

      For 語句

      Go 不具有 while 語句,也沒有 do-while 語句。可以賦以for語句一個單一條件,使其與while語句等效。完全省略條件會制造一個無限循環。

      for語句可能包含一個范圍條件,對strings, arrays, slices, maps, 或 channels進行迭代。除了這么寫

      for i := 0; i < len(a); i++ { ... }

      要循環遍歷a的元素,我們也可以這么寫

      for i, v := range a { ... }

      這會將i作為索引,給v賦以一個array, slice, 或 string中的連續元素。對于字符串,i是對單一字節的索引,v是一個符文類型的Unicode代碼點(符文是int32的一個別名)。對maps迭代會產生鍵值對,而對channels則只會產生一個迭代值。

      Break和Continue

      像Java一樣,Go允許使用break和continue來指定標簽(label),但是這個標簽所引用的必須是一個for、switch或者select語句。

      Switch語句

      在一個switch語句中,case標簽默認不會往下傳遞(fall through,也就是在沒有break的情況下也不會執行后續case的程序),但是你能夠通過使用一個向下傳遞(fallthrough)語句來使得它們可以向下傳遞。

      switch n {
      case 0:  // empty case body
      case 1:
          f()  // f is not called when n == 0.
      }

      但是一個case能夠有多個值:

      switch n {
      case 0, 1:
          f()  // f is called if n == 0 || n == 1.
      }

      case后面的值可以是支持相等比較(equality comparison)操作符的任何類型,比如string或者pointer。缺省的switch表達式等效于true表達式:

      switch {
      case n < 0:
          f1()
      case n == 0:
          f2()
      default:
          f3()
      }

      ++ 和 -- 語句

      ++ 和 --只能被用作語句中的后綴操作符,而不能用在表達式中。例如,你不能再這樣寫了: n = i++。

      defer 語句

      defer語句用來調用一個函數,但將其執行延遲到上一個附近的函數返回之后的時刻。被延遲的函數的執行與附近函數返回采取的路徑無關。然而,當defer語句執行時,被延遲函數的參數已經被計算并保存以供之后使用。

      f, err := os.Open("filename")
      defer f.Close()  // 當上個函數返回后,f 會被關閉.

      常量

      在 Go 中常量可以是無類型的。這適用于數值常量,只使用無類型常量的表達式,以及沒有給出類型和初始化表達式是無類型的常量聲明。當一個無類型的常量被用于一個需要類型化的值的環境中,它的值會轉換成類型化的。所以即使 Go 沒有隱式的類型轉換,這也能允許常量被相對自由地使用。

      var a uint
      f(a + 1)    // 無類型的數字常量 1 成為 uint 類型.
      f(a + 1e3)  // 1e3 也作為 uint 類型.
      語言在無類型的數值常數的大小上不強加任何限制。 只有當一個常數被用在需要一個有類型的值時才會根據該類型對大小有所限制,如:
      const huge = 1 << 100
      var n int = huge >> 98

      如果在一個類型聲明中類型關鍵字缺失,而相關的表達式計算出來是一個非類型(untyped)的數字常量,這個常量就會被分別轉換成rune、int、float64、或者complex128類型,取決于這個值是否是一個字符(character)、整形(integer)、浮點型(float-point)還是復雜型(complex)的常量。

      c := '?'      // rune (alias for int32)
      n := 1 + 2    // int
      x := 2.7      // float64
      z := 1 + 2i   // complex128

      Go沒有枚舉(enumerate)類型。取而代之,你可以在一個單獨的常量(const)聲明中使用特殊的名字iota來獲得一系列增長的值。當一個初始化表達簡化成一個常量時,它就會重用前面的表達式。

      const (
          red = iota  // red == 0
          blue        // blue == 1
          green       // green == 2
      )

      結構

      結構對應于 Java 中的類,但結構的成員不能是方法,只能是變量。結構指針類似 Java 中的引用變量。與 Java 的類不同,結構也可以被定義為直接值。對于結構和結構指針都可以使用“.”來訪問結構中的成員,如:

      type MyStruct struct {
          s string
          n int64
      }

      var x MyStruct // x 被初始化為 MyStruct{"", 0}. var px *MyStruct // 指針 px 初始化為 nil. px = new(MyStruct) // px 指向新結構體 MyStruct{"", 0}.

      x.s = "Foo" px.s = "Bar"</pre>

      在 Go 中,方法可以與任意命名類型有關,不只與結構; 參考方法和接口的討論.

      指針

      Java程序員快速入門 go 語言

      如果你有一個整數或一個結構或一個數組,賦值會復制對象的內容。Go 使用指針來實現 Java 的引用變量的效果。對于任意類型T,有一個相應的指針類型*T,表示指向類型T的值。

      要為一個指針變量分配存儲空間,要使用內置函數 new,它接受一個類型并返回一個指向已分配存儲空間的指針。分配的空間將根據類型進行零初始化。 例如,new(int)為一個新int分配存儲空間,初始值為0,并返回它的地址,類型為*int。


      在Java代碼 T p = new T()中,T是一個帶有兩個int類型實體變量a和b的類,對應于:

      type T struct { a, b int }
      var p *T = new(T)

      或者更地道的:

      p := new(T)

      聲明語句 var v T 聲明了一個裝著類型T的值的變量,在Java中沒有相匹配的語句。值也可以使用一種復合語法來創建或者初始化,例如:

      v := T{1, 2}

      等效于:

      var v T
      v.a = 1
      v.b = 2

      對于類型T的一個操作數x,尋址操作符 &x 提供x的地址,它是類型*T的一個值。例如:

      p := &T{1, 2} // p has type *T

      對于指針類型的一個操作數x,指針指向(pointer indirection)通過x用*x表示所指向的值。指針指向是很少被用到的;而Go像Java一樣,能夠自動獲取到一個變量的地址:

      p := new(T)
      p.a = 1 // equivalent to (*p).a = 1

      Slices

      一個slice是一個含有3個域的結構體:一個指向數組的指針,一個長度,一個容量大小。Slices可以用[]來訪問數組元素。內置的len函數返回slice的長度,cap函數返回容量大小。

      創建一個新的slice,可以用給定數組或slice a,通過a[i:j]的方式創建。新創建的slice是對a的引用,并且內容用是從a內容的索引的i到索引j。它的長度是j-i。如果i缺省,其slice開始于0,j缺省表示len(a)。新的slice是原來a的引用,如果改變了新slice里元素的值,a也會改變。新slice的容量是a的容量減去i。其數值的容量是原數值的長度。

      var s[]int
      var a[10]int
      
      s=a[:]//s=a[0:len(a)]的簡寫

      如果創建了一個數[100]byte(100bytes的數組,也許用作緩存區),并且想將它傳遞給一個函數,那么可以將函數的參數設置為[]byte類型,這樣就會傳遞一個slice。slice也可以通過make函數來創建(如下有描述)。

      和Java中的ArrayList用法一樣,slice也內建append函數。

      s0:=[]int{1,2}
      s1:=append(s0,3)//添加一個單元素
      s2:=append(s1,4,5)//添加多元素
      s3:=append(s2,s0...)//添加slice
      slice也能用于string,它將返回子字符串。

      初始化

      Map和channel的值必須用內建的函數make來申請值。例如,用

      make(map[string]int)
      將得到一個類型為map[string]int類型的值。于用new不同,make將返回對象值而不是地址。這樣就于map和channel為引用類型就保持一致了。

      對于map,make函數提供一個隱含可選的第二個參數。對于channel,也有第二個可選參數,它是用來設置channel緩沖區的大小;默認是0(沒有緩沖區)。

      make函數也能為slice來申請值。那樣將申請一個隱藏在slice里數組,而返回的是指向它的slice引用。此時用make需要一個slice元素個數的參數。第二個可選參數是slice的容量。

      m:=make([]int,10,20)//于new([20]int)[:10]相同

      方法和接口

      方法

      方法像是一個普通的函數定義,除非它有一個接收器(receiver)。接收器類似于Java實體方法中的this引用。

      type MyType struct { i int }
      
      func (p *MyType) Get() int {
          return p.i
      }
      
      var pm = new(MyType)
      var n = pm.Get()

      這里定義了一個同MyType聯系起來的Get方法。名為p的接收器在函數體之中。

      方法被定義在有命名的類型中。如果轉換成不同類型的值,新的值將會擁有新類型的方法,而不是原有類型的那些方法。

      你也許會在一個內建的類型中定義方法,通過聲明一個繼承自它的新命名的類型。新的類型同內建的類型是不同的。

      type MyInt int
      
      func (p MyInt) Get() int {
          return int(p)  // The conversion is required.
      }
      
      func f(i int) {}
      var v MyInt
      
      v = v * v          // The operators of the underlying type still apply.
      f(int(v))          // int(v) has no defined methods.
      f(v)               // INVALID

      接口

      Go的接口同Java的接口類似,但任何提供了用一個Go接口命名的方法的類型,都可以被看做是對那個接口的實現。不需要額外的聲明了。

      給定下面這個接口:

      type MyInterface interface {
          Get() int
          Set(i int)
      }
      由于MyType已經有了一個Get方法,我們能夠讓Mytype滿足這個接口的要求,通過添加:
      func (p *MyType) Set(i int) {
          p.i = i
      }
      現在任何使用MyInterface作為參數的函數,將可以接受 *MyType類型的變量:
      func GetAndSet(x MyInterface) {}
      
      func f1() {
          var p MyType
          GetAndSet(&p)
      }
      在Java中,為*MyType類型定義Set和Get,就會讓*MyType自動實現了MyInterface。一個類型可以滿足多個接口。這是一種鴨式類型(duck typing)的形式。
      當我看到一只鳥兒走起路來像鴨子,游起來也像鴨子,呱呱的叫起來也像鴨子,我就會把這只鳥兒稱作鴨子.
      James Whitcomb Riley 匿名域

      匿名域和Java中的子類類似.

      type MySubType struct {
          MyType
          j int
      }
      
      func (p *MySubType) Get() int {
          p.j++
          return p.MyType.Get()
      }
      MySubType是作為MyType的子類。
      func f2() {
          var p MySubType
      
          GetAndSet(&p)
      }
      Set方法繼承于MyType,在閉合類型中的匿名域的方法可以提升為閉合類型的方法。也就是說,MyType做為MySubType的匿名域,MyType中的方法MySubType都能用。Get方法是覆蓋方法,Set方法是繼承來的。
      匿名域和也不是和Java中的子類完全一樣。當調用匿名域的方法,方法的接收者是匿名域而不是閉合類型。也就是說,匿名域的方法不會動態分發。如果想實現Java中的動態方法looup,就等用interface。
      func f3() {
          var v MyInterface
      
          v = new(MyType)
          v.Get()  // Call the Get method for *MyType.
      
          v = new(MySubType)
          v.Get()  // Call the Get method for *MySubType.
      }

      類型斷言

      用類型斷言可以將變量從一個接口類型轉變為不同的接口類型。這是在運行時動態實現的。與Java不同,不需要對兩個接口之間的關系作任何聲明,如:

      type Printer interface {
          Print()
      }
      
      func f4(x MyInterface) {
          x.(Printer).Print()  // 類型斷言為 Printer
      }

      轉換到Printer完全是動態的。 只要x的動態類型(存儲在x中的值的實際類型)定義了一個Print方法,它就會工作。

      泛型

      Go 沒有泛型類型,但通過結合匿名字段和類型斷言可以實現類似于Java的參數化的類型,如:

      type StringStack struct {
          Stack
      }
      
      func (s *StringStack) Push(n string) { s.Stack.Push(n) }
      func (s *StringStack) Pop() string   { return s.Stack.Pop().(string) }

      StringStack限定Hello stack例子中的泛型Stack,所以它只操作字符串元素——就像Java中的Stack 。注意,Sizemethod繼承于Stack。

      錯誤

      Java經常使用異常,Go則有兩種機制。大多數程序只有真正不能回收的情況下返回錯誤,比如超出范圍的索引,產生一個運行時異常。

      Go的多值返回使得返回一個詳細的錯誤消息和正常的返回值十分容易。按照慣例,這些消息有類型錯誤,一個簡單的內置接口。

      type error interface {
          Error() string
      }
      舉個例子,如果打開文件失敗,os.Open函數返回一個非空錯誤值。
      func Open(name string) (file *File, err error)
      下面的代碼使用os.Open來打開一個文件。如果遇到錯誤就會調用log.Fatal來打印錯誤消息并終止。
      f, err := os.Open("filename.ext")
      if err != nil {
          log.Fatal(err)
      }
      // do something with the open *File f

      錯誤接口只需要一個Error方法,但是特定的錯誤實現往往會有附加的方法,允許調用者檢查詳細的錯誤。

      Panic和recover

      一個panic是一個運行時刻錯誤,并釋放Go程序的堆棧,同時運行defer程序,最終程序停止。Panic和Java中的異常相似,但它僅僅表明是運行時錯誤,比如空指針或是數組越界。Go用內建錯誤類型來描述如訪問文件結尾等上述錯誤信息。

      內建函數recover可用在panic并內恢復Go程序運行,同時recover能停止循環返回參數傳遞給panic。因為在defer函數里只能運行循環代碼,同時revcover只能運行defer函數中。如果Go程序沒有panic,recover將返回nil。

      Go協程和信道

      Java程序員快速入門 go 語言
      壽司制作流程

      Go協程

      Go中的線程,使用go聲明,執行一個goroutine.并且在不同的,新創建的goroutine中運行該函數.在一個程序中所有的Go協程,共用相同的地址空間.

      Go協程是輕量級的,消耗成本只比分配的棧空間多一點,  棧開始時較小并通過堆存儲的分配和釋放來實現其增長。內部的Go協程像協程一樣并存在操作系統的多個線程中。你不必去拘泥于這些細節。

      go list.Sort()  // Run list.Sort in parallel; don’t wait for it.
      Go 擁有函數字面量,可以表現為閉包函數,與go 聲明一起使用的話,功能將更為強大.
      // 發布后打印文本到標準輸出,直到給定的時間過期。
      func Publish(text string, delay time.Duration) {
          go func() {
              time.Sleep(delay)
              fmt.Println(text)
          }()  // 注意括號,我們必須調用該函數.
      }

      變量text和delay在外部函數和閉包(函數字面量)之間共享,只存在于在它們可訪問期間。

      Channels

      channel 通過傳遞特定元素類型的值,提供了一套兩個 goroutines 同步執行及交流的機制。  <-  操作符制定 channel 發送或接收的方向。如果沒有明確方向,則通道為雙向的。

      chan Sushi      // can be used to send and receive values of type Sushi
      chan<- float64  // can only be used to send float64s
      <-chan int      // can only be used to receive ints

      Channel 為引用類型,使用 make 分配。

      ic := make(chan int)        // unbuffered channel of ints
      wc := make(chan *Work, 10)  // buffered channel of pointers to Work

      要向通道傳遞值,以二進制操作符的方式使用 <- 。要接收數據,則以一元運算符的方式使用它。

      ic <- 3       // Send 3 on the channel.
      work := <-wc  // Receive a pointer to Work from the channel.

      如若 channel 無緩沖區,則發送者堵塞,直到接收者接受到傳值。如果 channel 有緩沖區,發送者堵塞,直到接收者開始讀取緩沖區;如果緩沖區滿了,則需要等到某些接收者開始檢索值。接收者堵塞,直到有數據可接收。

      close 函數將記錄通道不能再被用來發送數據。當調用 close 函數后,在所有先前發送的值都被接收以后,接收操作將不會堵塞,同時返回 0 值。一個多值的接收操作將能夠獲取到 channel 是否被關閉的指示。

      ch := make(chan string)
      go func() {
          ch <- "Hello!"
          close(ch)
      }()
      fmt.Println(<-ch)  // Print "Hello!".
      fmt.Println(<-ch)  // Print the zero value "" without blocking.
      fmt.Println(<-ch)  // Once again print "".
      v, ok := <-ch      // v is "", ok is false.

      在下面的例子,我們將使 Publish 函數返回一個通道,它將被用來在文本發表完成后廣播消息

      // Publish prints text to stdout after the given time has expired.
      // It closes the wait channel when the text has been published.
      func Publish(text string, delay time.Duration) (wait <-chan struct{}) {
          ch := make(chan struct{})
          go func() {
              time.Sleep(delay)
              fmt.Println(text)
              close(ch)
          }()
          return ch
      }

      下面就是 Publish 函數的大概用法

      wait := Publish("important news", 2 * time.Minute)
      // Do some more work.
      <-wait // blocks until the text has been published

      Select語句

      select語句是Go統一工具箱中的最終工具。它選擇哪些通信將被處理。如果任何的通信都能處理,那么就會隨機選其一,與之對應的語句就會執行。另外,如果沒有默認的case,語句就會阻塞直到其中一個通信完成為止。

      這里有一個toy的例子,展示了select語句如何用來實現一個隨機數發生器。

      rand := make(chan int)
      for { // Send random sequence of bits to rand.
          select {
          case rand <- 0: // note: no statement
          case rand <- 1:
          }
      }
      更加現實的是,這里有一個select語句可以用來設置時間限制一個接受操作。
      select {
      case news := <-AFP:
          fmt.Println(news)
      case <-time.After(time.Minute):
          fmt.Println("Time out: no news in one minute.")
      }

      time.After函數式標準庫的一部分;它等待一段特定時間后發送當前時間到返回的頻道。

      并發(示例)

      最后我們通過一個小而全的例子展示如何將若干塊拼湊在一起。它是一個服務器通過一個頻道(channel)接受Work請求(Work request)的草案代碼。每一個請求都使用一個單獨的渠道進行處理。Work將自身構造包含進一個用來返回結果的頻道中。

      package server
      
      import "log"
      
      // New creates a new server that accepts Work requests
      // through the req channel.
      func New() (req chan<- *Work) {
          wc := make(chan *Work)
          go serve(wc)
          return wc
      }
      
      type Work struct {
          Op    func(int, int) int
          A, B  int
          Reply chan int  // Server sends result on this channel.
      }
      
      func serve(wc <-chan *Work) {
          for w := range wc {
              go safelyDo(w)
          }
      }
      
      func safelyDo(w *Work) {
          // Regain control of panicking goroutine to avoid
          // killing the other executing goroutines.
          defer func() {
              if err := recover(); err != nil {
                  log.Println("work failed:", err)
              }
          }()
          do(w)
      }
      
      func do(w *Work) {
          w.Reply <- w.Op(w.A, w.B)
      }
      而下面這是你如何使用它:
      package server_test
      
      import (
          server "."
          "fmt"
          "time"
      )
      
      func main() {
          s := server.New()
      
          divideByZero := &server.Work{
              Op:    func(a, b int) int { return a / b },
              A:     100,
              B:     0,
              Reply: make(chan int),
          }
          s <- divideByZero
      
          select {
          case res := <-divideByZero.Reply:
              fmt.Println(res)
          case <-time.After(time.Second):
              fmt.Println("No result in one second.")
          }
          // Output: No result in one second.
      }
      并發是一個龐大的話題,而Go的方法和Java的方法時相當不同的。有兩篇文章涉及到了這些基礎:

      并發編程基礎(Fundamentals of concurrent programming )使用Go編寫的小例子來介紹并發。

      通過交流共享內存(Share Memory by Communicating )使用更大幅度的例子進行代碼走讀(codewalk)。

      Stefan Nilsson

       本文由用戶 jopen 自行上傳分享,僅供網友學習交流。所有權歸原作者,若您的權利被侵害,請聯系管理員。
       轉載本站原創文章,請注明出處,并保留原始鏈接、圖片水印。
       本站是一個以用戶分享為主的開源技術平臺,歡迎各類分享!
      var v1 int int v1 = 0;
      var v2 *int Integer v2 = null;
      var v3 string String v3 = "";
      var v4 [10]int int[] v4 = new int[10]; // v4 is a value in Go.
      var v5 []int int[] v5 = null;
      var v6 *struct { a int }   C v6 = null; // Given: class C { int a; }
      var v7 map[string]int HashMap v7 = null;
      var v8 func(a int) int F v8 = null; // interface F { int f(int a); }
sesese色