1. 程式人生 > 實用技巧 >go-web摘抄1-基礎知識

go-web摘抄1-基礎知識

go 語言基礎

1. hello,world

package main

import "fmt"

func main() {
    fmt.Printf("Hello, world or 你好,世界 or καλημ ́ρα κóσμ or こんにちはせかい\n")
}

2. go 基礎

2.1 變數

定義變數:

var variableName type //宣告一個變數,並未賦值
var vname1, vname2, vname3 type //宣告多個變數
var variableName type = value //初始化“variableName”的變數為“value”值,型別是“type”
var vname1, vname2, vname3 type= v1, v2, v3
_, b := 34, 35 //_(下劃線)是個特殊的變數名,任何賦予它的值都會被丟棄
vname1, vname2, vname3 := v1, v2, v3 //:=這個符號直接取代了var和type,這種形式叫做簡短宣告。不過它有一個限制,那就是它只能用在函式內部
var variable1 = v1 //省略type
var (
	varable1 int,
    varable2 string
    //...
) //少寫一些var

一般用var方式來定義全域性變數

2.2 常量

const constantName = value //常量可以是任何型別,所以可以不用寫型別
//如果需要,也可以明確指定常量的型別:
const Pi float32 = 3.1415926

特殊常量iota

它預設開始值是0,const中每增加一行加1:

package main

import "fmt"

const (
	a = iota
	b
	c = 123
	d
)

func main() {
	fmt.Println(a, b, c, d)
}

2.3 內建基礎型別

2.3.1 boolean 布林型別

var isActive bool// 全域性變數宣告
var enabled, disabled = true,false //忽略型別
func test(){
    var avaiable bool
    var := false
    available = true
}

在Go中,布林值的型別為bool,值是truefalse,預設為false

2.3.2 數值型別

整數:

整數型別有無符號和帶符號兩種。Go同時支援intuint,這兩種型別的長度相同,但具體長度取決於不同編譯器的實現。Go裡面也有直接定義好位數的型別:rune, int8, int16, int32, int64byte, uint8, uint16, uint32, uint64。其中runeint32的別稱,byteuint8的別稱。

tip 注意: 這些型別的變數之間不允許互相賦值或操作,不然會在編譯時引起編譯器報錯。 int8和uint8是兩種不同的型別

浮點型

浮點數的型別有float32

float64兩種(沒有float型別),預設是float64

複數:

Go還支援複數。它的預設型別是complex128(64位實數+64位虛數)。如果需要小一些的,也有complex64(32位實數+32位虛數)

字串:

Go中的字串都是採用UTF-8字符集編碼。字串是用一對雙引號("")或反引號(` )括起來定義,它的型別是string`

//示例程式碼
var frenchHello string  // 宣告變數為字串的一般方法
var emptyString string = ""  // 聲明瞭一個字串變數,初始化為空字串
func test() {
    no, yes, maybe := "no", "yes", "maybe"  // 簡短宣告,同時宣告多個變數
    japaneseHello := "Konichiwa"  // 同上
    frenchHello = "Bonjour"  // 常規賦值
}

字串裡面的值是不能被更改的,如果要修改,可以轉成byte型別再轉字串

var s string = "hello"
s[0] = 'c //程式碼編譯時會報錯:cannot assign to s[0]
//一定要更改的話
s := "hello"
c := []byte(s)  // 將字串 s 轉換為 []byte 型別
c[0] = 'c'
s2 := string(c)  // 再轉換回 string 型別
fmt.Printf("%s\n", s2)
  • 字串連線: +

  • 多行字串

    m := `hello
        world` 
    

陣列:

陣列定義方式:

var arr [n]type // n是長度 type是型別

對陣列的操作和其它語言類似,都是通過[]來進行讀取或賦值

var arr [10]int  // 聲明瞭一個int型別的陣列
arr[0] = 42      // 陣列下標是從0開始的
arr[1] = 13      // 賦值操作
fmt.Printf("The first element is %d\n", arr[0])  // 獲取資料,返回42
fmt.Printf("The last element is %d\n", arr[9]) //返回未賦值的最後一個元素,預設返回0
c := [...]int{4, 5, 6} // 可以省略長度而採用`...`的方式,Go會自動根據元素個數來計算長度
// 聲明瞭一個二維陣列,該陣列以兩個陣列作為元素,其中每個陣列中又有4個int型別的元素
doubleArray := [2][4]int{[4]int{1, 2, 3, 4}, [4]int{5, 6, 7, 8}}
// 上面的宣告可以簡化,直接忽略內部的型別
easyArray := [2][4]int{{1, 2, 3, 4}, {5, 6, 7, 8}}

切片slice

slice 是陣列的檢視,他指向一個底層陣列.

切片定義:

var fslice []int //和定義陣列一直,只是少了長度

slice可以從一個數組或一個已經存在的slice中再次宣告

// 宣告一個含有10個元素元素型別為byte的陣列
var ar = [10]byte {'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j'}

// 宣告兩個含有byte的slice
var a, b []byte

// a指向陣列的第3個元素開始,併到第五個元素結束,左閉又開
a = ar[2:5]
//現在a含有的元素: ar[2]、ar[3]和ar[4]

// b是陣列ar的另一個slice
b = ar[3:5]
// b的元素是:ar[3]和ar[4]

slice的示例:

// 宣告一個數組
var array = [10]byte{'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j'}
// 宣告兩個slice
var aSlice, bSlice []byte

// 演示一些簡便操作
aSlice = array[:3] // 等價於aSlice = array[0:3] aSlice包含元素: a,b,c
aSlice = array[5:] // 等價於aSlice = array[5:10] aSlice包含元素: f,g,h,i,j
aSlice = array[:]  // 等價於aSlice = array[0:10] 這樣aSlice包含了全部的元素

// 從slice中獲取slice
aSlice = array[3:7]  // aSlice包含元素: d,e,f,g,len=4,cap=7
bSlice = aSlice[1:6] // bSlice 包含aSlice[1], aSlice[2] 也就是含有: e,f
fmt.Println(aSlice)
fmt.Println(bSlice)
bSlice = aSlice[:3]  // bSlice 包含 aSlice[0], aSlice[1], aSlice[2] 也就是含有: d,e,f
bSlice = aSlice[0:5] // 對slice的slice可以在cap範圍內擴充套件,此時bSlice包含:d,e,f,g,h
bSlice = aSlice[:]   // bSlice包含所有aSlice的元素: d,e,f,g

map欄位:

map[keyType]valueType

使用例項

// 初始化一個字典
rating := map[string]float32{"C":5, "Go":4.5, "Python":4.5, "C++":2 }
// map有兩個返回值,第二個返回值,如果不存在key,那麼ok為false,如果存在ok為true
csharpRating, ok := rating["C#"]
if ok {
    fmt.Println("C# is in the map and its rating is ", csharpRating)
} else {
    fmt.Println("We have no rating associated with C# in the map")
}

delete(rating, "C")  // 刪除key為C的元素

錯誤型別

go內建一個error型別

err := errors.New("emit macho dwarf: elf header corrupted")
if err != nil {
    fmt.Print(err)
}

new 與make操作:

make用於內建型別(mapslicechannel)的記憶體分配。new用於各種型別的記憶體分配。

內建函式make(T, args)new(T)有著不同的功能,make只能建立slicemapchannel,並且返回一個有初始值(非零)的T型別,而不是*T。本質來講

3. 流程和函式

3.1 條件判斷

  • if判斷

Go的if還有一個強大的地方就是條件判斷語句裡面允許宣告一個變數,這個變數的作用域只能在該條件邏輯塊內,其他地方就不起作用了

if x:=computedValue();x>10{
    //...
    fmt.Println("x is greater than 10");
}else{
    fmt.Println("x is less than 10");
}
  • switch

    switch sExpr{
        case expr1:
        	some instructions
        case expr2:
        	some instructions
        case expr3:
        	some instructions
        default:
        	other code
    }
    

    sExprexpr1,expr2,expr3的型別必須一致.go 預設每個case後面都帶有一個 break,如果強制執行後面的的,可以使用fallthrough,注意:如果sExpr是一個表示式的話後面的case型別就是bool型別了,比如switch a>1{...}

3.2 迴圈

  • for迴圈:

    語法

    for expression1; expression2; expression3 {
        //...
    }
    

    expression1expression2expression3都是表示式,其中expression1expression3是變數宣告或者函式呼叫返回值之類的,expression2是用來條件判斷,expression1在迴圈開始之前呼叫,expression3在每輪迴圈結束之時呼叫。

    示例:

    for x, y := 1, 2; x <= 10; x++ {
    		y++
    		fmt.Println(x)
    }
    //...
    //省略所有迴圈條件,形成while(true判斷)
    for {
       //...
    }
    
    //
    func main() {
    	x := 1
    	for x < 10 {
    		x++
    		fmt.Println(x)
    	}
    }
    

3.3 無條件跳轉

  • go

    goto跳轉到必須在當前函式內定義的標籤

    func myfunc(){
        i:=0
    Here:
        //
        fmt.Println(i)
        i++
        goto Here
    }
    

3.4函式

函式是Go裡面的核心設計,它通過關鍵字func來宣告,它的格式如下:

func funName(input1 type1,input2 type2)(output1 type1,output2 type2){
    //...
    return value1,value2
}

最好命名返回值

func SumAndProduct(A, B int) (add int, Multiplied int) {
    add = A+B
    Multiplied = A*B
    return
}
  • 變參:

go函式支援變參,接受不定數量的引數,型別需要一致

func funcName(arg... int){}
  • 傳值與傳指標:

    通過函式改變引數的值時,函式外的引數不會受到影響,應為傳入的是外層變數的一個copy

    package main
    
    import (
    	"fmt"
    )
    
    func main() {
    	param := 1
    	add1(&param)
    	fmt.Println("this is param ", param)
    }
    func add1(param *int) int {
    	*param++
    	fmt.Println("here add1 ", *param)
    	return *param
    }
    
  • 指標的好處:

    傳指標可以使多個函式操作同一個物件

    大的結構體時,用指標比較好

3.5 defer

defer 語義為延時, 當函式執行到最後時,這些defer語句會按照逆序執行,最後該函式返回

func main() {
	defer fmt.Println(1)
	defer fmt.Println(2)
	defer fmt.Println(3)
	defer fmt.Println("start...")
}
/**
列印結果如下:
start...
3
2
1
*/

3.6 函式作為值,型別

在Go中函式也是一種變數,我們可以通過type來定義它,它的型別就是所有擁有相同的引數,相同的返回值的一種型別

type typeName func(input1 inputType1 , input2 inputType2 [, ...]) (result1 resultType1 [, ...])

示例說明

package main
import "fmt"

type testInt func(int) bool // 聲明瞭一個函式型別

func isOdd(integer int) bool {
    if integer%2 == 0 {
        return false
    }
    return true
}

func isEven(integer int) bool {
    if integer%2 == 0 {
        return true
    }
    return false
}

// 宣告的函式型別在這個地方當做了一個引數

func filter(slice []int, f testInt) []int {
    var result []int
    for _, value := range slice {
        if f(value) {
            result = append(result, value)
        }
    }
    return result
}

func main(){
    slice := []int {1, 2, 3, 4, 5, 7}
    fmt.Println("slice = ", slice)
    odd := filter(slice, isOdd)    // 函式當做值來傳遞了
    fmt.Println("Odd elements of slice are: ", odd)
    even := filter(slice, isEven)  // 函式當做值來傳遞了
    fmt.Println("Even elements of slice are: ", even)
}

函式作為型別,就是定義一個型別,作為引數/變數,在賦值時,變為具體的函式來使用.類似 一層抽象的過程.

3.7 panic 和recover

go沒有類似java的丟擲異常的機制,它不能丟擲異常,而是使用pannic和recover的方式. 一定要記住,你應當把它作為最後的手段來使用,也就是說,你的程式碼中應當沒有,或者很少有panic的東西

  • panic:

3.8 main函式和init函式

maininit函式是go的兩個保留函式,init函式可以作用在任何包,main只能作用域package main的包裡.

init類似其他語言中的構造方法,在初始化變數和常量後,會呼叫init

3.9 import

import常用的幾種方式:

  • 點操作:

    這個點操作的含義就是這個包匯入之後在你呼叫這個包的函式時,你可以省略字首的包名, 也就是前面你呼叫的fmt.Println("hello world")可以省略的寫成Println("hello world")

     import(
         . "fmt"
     )
    
  • 別名操作

    可以把包命名成另一個我們用起來容易記憶的名字

    import(
    	f "fmt"
    )
    
  • _操作

    _操作其實是引入該包,而不直接使用包裡面的函式,而是呼叫了該包裡面的init函式。

     import (
         "database/sql"
         _ "github.com/ziutek/mymysql/godrv"
     )
    

4. struct結構體

struct 類似c 語言中的結構體,可以通過他來實現一些物件功能

定義struct結構體如下:

type person struct {
	name string
	age  int
}

使用結構體:

var p person
p.name = "zhagsna"
p.age = 21
fmt.Println(p.name)
//另一種使用
p:=person{"tom",21}
p:=person{age:21}
P:= new(person) //通過new 函式分配一個指標,此處型別為*p

匿名欄位:

type Human struct {
    name string
    age int
    weight int
}

type Student struct {
    Human  // 匿名欄位,那麼預設Student就包含了Human的所有欄位
    speciality string
}

5. 面向物件

5.1 method

struct中的類似其他語言中的方法定義方式如下:

func (r ReceiverType) funcName(parameters) (results)

如果在方法名前有一個接受者相當於給某個型別增加了一個方法,不僅僅限於struct

type money float32

func (m money) add() {
	fmt.Println("Now im in money")
}

func main() {
	var money money = 12
	money.add()

}

別名money 增加方法add

5.2 指標作為reciver

5.3 method方法繼承

如果匿名欄位實現了一個method,那麼包含這個匿名欄位的struct也能呼叫該method

package main
import "fmt"

type Human struct {
    name string
    age int
    phone string
}

type Student struct {
    Human //匿名欄位
    school string
}

type Employee struct {
    Human //匿名欄位
    company string
}

//在human上面定義了一個method
func (h *Human) SayHi() {
    fmt.Printf("Hi, I am %s you can call me on %s\n", h.name, h.phone)
}

func main() {
    mark := Student{Human{"Mark", 25, "222-222-YYYY"}, "MIT"}
    sam := Employee{Human{"Sam", 45, "111-888-XXXX"}, "Golang Inc"}

    mark.SayHi()
    sam.SayHi()
}

5.4 method方法重寫

method方法的重寫,上面的SayHi 的接收者更改一下就可以了,或者再寫一個新的

6. interface 介面

6.1 什麼是interface

interface是一組method組合,我們通過interface定義介面的一組行為

6.2 interface 型別

type Human struct {
    name string
    age int
    phone string
}

type Student struct {
    Human //匿名欄位Human
    school string
    loan float32
}

type Employee struct {
    Human //匿名欄位Human
    company string
    money float32
}

//Human物件實現Sayhi方法
func (h *Human) SayHi() {
    fmt.Printf("Hi, I am %s you can call me on %s\n", h.name, h.phone)
}

// Human物件實現Sing方法
func (h *Human) Sing(lyrics string) {
    fmt.Println("La la, la la la, la la la la la...", lyrics)
}

//Human物件實現Guzzle方法
func (h *Human) Guzzle(beerStein string) {
    fmt.Println("Guzzle Guzzle Guzzle...", beerStein)
}

// Employee過載Human的Sayhi方法
func (e *Employee) SayHi() {
    fmt.Printf("Hi, I am %s, I work at %s. Call me on %s\n", e.name,
        e.company, e.phone) //此句可以分成多行
}

//Student實現BorrowMoney方法
func (s *Student) BorrowMoney(amount float32) {
    s.loan += amount // (again and again and...)
}

//Employee實現SpendSalary方法
func (e *Employee) SpendSalary(amount float32) {
    e.money -= amount // More vodka please!!! Get me through the day!
}

// 定義interface
type Men interface {
    SayHi()
    Sing(lyrics string)
    Guzzle(beerStein string)
}

type YoungChap interface {
    SayHi()
    Sing(song string)
    BorrowMoney(amount float32)
}

type ElderlyGent interface {
    SayHi()
    Sing(song string)
    SpendSalary(amount float32)
}

interface可以被任意物件實現,任意物件都實現了空interface

6.3 interface值

interface是一組抽象方法的集合,裡面沒有變數,他必須由其他非interface型別實現,

示例:


type Human struct {
    name string
    age int
    phone string
}
type Student struct {
    Human //匿名欄位
    school string
    loan float32
}

//Human實現SayHi方法
func (h Human) SayHi() {
    fmt.Printf("Hi, I am %s you can call me on %s\n", h.name, h.phone)
}

//Human實現Sing方法
func (h Human) Sing(lyrics string) {
    fmt.Println("La la la la...", lyrics)
}
//men介面
type Men interface {
    SayHi()
    Sing(lyrics string)
}

//...
//介面型別的變數,他的值可以是任意實現了該介面的物件型別
var i Men== Student{Human{"Mike", 25, "222-222-XXX"}, "MIT", 0.00}
i.SayHi()

6.4 interface引數

interface的變數可以持有任意實現interface型別的物件.

實現接受所有引數的方法,例如fmt.Println()

fmt庫實現了Stringer介面

type Stringer interface {
     String() string
}

go的fmt部分原始碼如下:

func Fprintln(w io.Writer, a ...interface{}) (n int, err error) {
    p := newPrinter()
    p.doPrintln(a)
    n, err = w.Write(p.buf)
    p.free()
    return
}
 
// Println 使用預設格式對 a 中提供的 arg 進行格式化,並將結果寫入標準輸出。
// 返回寫入的位元組數和錯誤資訊。
// 各 arg 之間會新增空格,並在最後新增換行符。
func Println(a ...interface{}) (n int, err error) {
    return Fprintln(os.Stdout, a...)
}

6.5 變數儲存的型別

判斷一個變數是不是屬於某一個實現型別,類似於php中的instanceof 目前有兩種方式如下:

  • Comma-ok:

    Go語言裡面有一個語法,可以直接判斷是否是該型別的變數: value, ok = element.(T),這裡value就是變數的值,ok是一個bool型別,element是interface變數,T是斷言的型別

    type Element interface{} //定義這個型別是為了接受任意型別的變數
    type List [] Element
    
    ...
    //定義多型別的的陣列物件
    list := make(List, 3)
    list[0] = 1 // an int
    list[1] = "Hello" // a string
    list[2] = Person{"Dennis", 70}
    
    for index, element := range list {
        if value, ok := element.(int); ok {
            fmt.Printf("list[%d] is an int and its value is %d\n", index, value)
        } else if value, ok := element.(string); ok {
            fmt.Printf("list[%d] is a string and its value is %s\n", index, value)
        } else if value, ok := element.(Person); ok {
            fmt.Printf("list[%d] is a Person and its value is %s\n", index, value)
        } else {
            fmt.Printf("list[%d] is of a different type\n", index)
        }
    }
    
  • switch 測試

    只是換了一種方式,晒his利用elelmet.(type)的形式

     for index, element := range list{
         switch value := element.(type) {
             case int:
             fmt.Printf("list[%d] is an int and its value is %d\n", index, value)
             case string:
             fmt.Printf("list[%d] is a string and its value is %s\n", index, value)
             case Person:
             fmt.Printf("list[%d] is a Person and its value is %s\n", index, value)
             default:
             fmt.Println("list[%d] is of a different type", index)
         }
     }
    
    
    

6.6 嵌入interface

一個interface1作為interface2的一個嵌入欄位

//io包下面的 io.ReadWriter ,它包含了io包下面的Reader和Writer兩個interface:
// io.ReadWriter
type ReadWriter interface {
    Reader
    Writer
}

6.7 反射reflect

所謂反射就是檢測程式執行時的狀態.

使用reflect包的三個步驟

  • 要去反射是一個型別的值(這些值都實現了空interface),首先需要把它轉化成reflect物件(reflect.Type或者reflect.Value,根據不同的情況呼叫不同的函式)。這兩種獲取方式如下
t := reflect.TypeOf(i)    //得到型別的元資料,通過t我們能獲取型別定義裡面的所有元素
v := reflect.ValueOf(i)   //得到實際的值,通過v我們獲取儲存在裡面的值,還可以去改變值

使用例項:

import (
	"fmt"
	"reflect"
)

type Person struct {
	name string
	age  int
}

func main() {
	var x Person
	x.name = "張三"
	x.age = 12
	v := reflect.TypeOf(x) //轉化成reflect型別
	v1 := reflect.ValueOf(x)
	fmt.Println("type:", v)
	fmt.Println(v1)

}
  • 轉化為reflect物件之後我們就可以進行一些操作了,也就是將reflect物件轉化成相應的值,例如
tag := t.Elem().Field(0).Tag  //獲取定義在struct裡面的標籤
name := v.Elem().Field(0).String()  //獲取儲存在第一個欄位裡面的值
  • 最後,反射的話,那麼反射的欄位必須是可修改的,我們前面學習過傳值和傳引用,這個裡面也是一樣的道理。反射的欄位必須是可讀寫的意思是,如果下面這樣寫,那麼會發生錯誤
var x float64 = 3.4	
v := reflect.ValueOf(x)
v.SetFloat(7.1)

7. 併發

7.1 goroutine

goroutine說到底其實就是執行緒,但是它比執行緒更小,十幾個goroutine可能體現在底層就是五六個執行緒,Go語言內部幫你實現了這些goroutine之間的記憶體共享。

goroutine是通過Go的runtime管理的一個執行緒管理器。goroutine通過go關鍵字實現了,其實就是一個普通的函式。

gorountine語法如下:

go hello(a,b,c)

使用示例:

package main

import (
	"fmt"
	"runtime"
)

type Person struct {
	name string
	age  int
}

func say(s string) {
	for i := 0; i < 5; i++ {
		runtime.Gosched() //runtime.Gosched()表示讓CPU把時間片讓給別人,下次某個時候繼續恢復執行該goroutine
		fmt.Println(s)
	}
}
func main() {
	go say("world")
	say("hello")

7.2 channels

goroutine執行在相同的地址空間,因此共享記憶體必須做好同步.goroutine之間通過channel進行資料通訊.

goroutine 通過channel進行傳送和接收資料,但這些數值必須是channel型別,需要注意的是建立channel時,必須使用make建立channel

channel通過操作符<-來接收和傳送資料

ch <- v    // 傳送v到channel ch.
v := <-ch  // 從ch中接收資料,並賦值給v

使用例項:

啟動一個goroutine,啟動goroutine 其實是一個非同步的步驟,所以使用return 是存在問題的,如果遇到耗時的比如io操作,會長時間得不到響應.所以需要通過channel來進行特殊處理,進行資料的傳輸

package main

import "fmt"

func sum(a []int, c chan int) {
	total := 0
	for _, v := range a {
		total += v
	}
	c <- total // send total to c
}

func main() {
	a := []int{7, 2, 8, -9, 4, 0} //定義陣列
	c := make(chan int)     //建立channel, chan這個是個關鍵字啊
	go sum(a[:len(a)/2], c) //啟動一個goroutine
	go sum(a[len(a)/2:], c)
	x, y := <-c, <-c // receive from c

	fmt.Println(x, y, x+y)
}

預設情況下,channel接收和傳送資料都是阻塞的,除非另一端已經準備好,這樣就使得Goroutines同步變的更加的簡單,而不需要顯式的lock。 所謂阻塞,也就是如果讀取(value := <-ch)它將會被阻塞,直到有資料接收。其次,任何傳送(ch<-5)將會被阻塞,直到資料被讀出。無緩衝channel是在多個goroutine之間同步很棒的工具。

7.3 Buffered channels

上面我們介紹了預設的非快取型別的channel,不過Go也允許指定channel的緩衝大小,很簡單,就是channel可以儲存多少元素。ch:= make(chan bool, 4),建立了可以儲存4個元素的bool 型channel。在這個channel 中,前4個元素可以無阻塞的寫入。當寫入第5個元素時,程式碼將會阻塞,直到其他goroutine從channel 中讀取一些元素,騰出空間。

ch := make(chan type, value)

value == 0 ! 無緩衝(阻塞)
value > 0 ! 緩衝(非阻塞,直到value 個元素)

例項:

package main

import "fmt"

func main() {
    c := make(chan int, 2)//修改2為1就報錯,修改2為3可以正常執行
    c <- 1
    c <- 2
    fmt.Println(<-c)
    fmt.Println(<-c)
}
    //修改為1報如下的錯誤:
    //fatal error: all goroutines are asleep - deadlock!

說明一下為什麼會報錯:

  • 因為channel是溝通goroutine的橋樑,預設的情況,channel接收和傳送資料都是阻塞的.

  • 因為channel是一個全雙工的的收發,但是進行一次資訊的互動,需要兩端同時做好準備才能進行通訊,

  • 阻塞時必須兩端都準備好連線關係,比如一端監聽某個埠,

  • 無快取式的channel會報錯,因為傳送和接收之間的關係沒有建立,如果是快取式的相當於將傳送過這快取起來,做一個類似連線池的東西,在使用時對裡面的傳送端進行配對,等接收端數量大於傳送端數量時,又會產生阻塞的情況.

7.4 Range和Close

上面這個例子中,我們需要讀取兩次c,這樣不是很方便,Go考慮到了這一點,所以也可以通過range,像操作slice或者map一樣操作快取型別的channel

package main

import (
    "fmt"
)

func fibonacci(n int, c chan int) {
    x, y := 1, 1
    for i := 0; i < n; i++ {
        c <- x
        x, y = y, x + y
    }
    close(c)
}

func main() {
    c := make(chan int, 10)
    go fibonacci(cap(c), c)
    for i := range c {
        fmt.Println(i)
    }
}

for i := range c能夠不斷的讀取channel裡面的資料,直到該channel被顯式的關閉。上面程式碼我們看到可以顯式的關閉channel,生產者通過內建函式close關閉channel。關閉channel之後就無法再發送任何資料了,在消費方可以通過語法v, ok := <-ch測試channel是否被關閉。如果ok返回false,那麼說明channel已經沒有任何資料並且已經被關閉。

7.5 select

對於多個channel的情況,可以使用關鍵字select,通過select監聽channel上面的資料流動

select 預設也是阻塞的,只有當監聽的channel中可以正常傳送或接收時,select才會執行,當多個channel都準備好是,select會隨機選擇一個執行

package main

import "fmt"

func fibonacci(c, quit chan int) {
    x, y := 1, 1
    for {
        select {
        case c <- x:
            x, y = y, x + y
        case <-quit:
            fmt.Println("quit")
            return
        }
    }
}

func main() {
    c := make(chan int)
    quit := make(chan int)
    go func() {
        for i := 0; i < 10; i++ {
            fmt.Println(<-c)
        }
        quit <- 0
    }()
    fibonacci(c, quit)
}

7.6 超時

有時候會出現goroutine阻塞的情況,那麼我們如何避免整個程式進入阻塞的情況呢?我們可以利用select來設定超時,通過如下的方式實現:

func main() {
    c := make(chan int)
    o := make(chan bool)
    go func() {
        for {
            select {
                case v := <- c:
                    println(v)
                case <- time.After(5 * time.Second):
                    println("timeout")
                    o <- true
                    break
            }
        }
    }()
    <- o
}

7.6 runtime goroutine

runtime包中有幾個處理goroutine的函式:

  • Goexit

    退出當前執行的goroutine,但是defer函式還會繼續呼叫

  • Gosched

    讓出當前goroutine的執行許可權,排程器安排其他等待的任務執行,並在下次某個時候從該位置恢復執行。

  • NumCPU

    返回 CPU 核數量

  • NumGoroutine

    返回正在執行和排隊的任務總數

  • GOMAXPROCS

    用來設定可以平行計算的CPU核數的最大值,並返回之前的值。