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
,值是true
或false
,預設為false
2.3.2 數值型別
整數:
整數型別有無符號和帶符號兩種。Go同時支援int
和uint
,這兩種型別的長度相同,但具體長度取決於不同編譯器的實現。Go裡面也有直接定義好位數的型別:rune
, int8
, int16
, int32
, int64
和byte
, uint8
, uint16
, uint32
, uint64
。其中rune
是int32
的別稱,byte
是uint8
的別稱。
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
用於內建型別(map
、slice
和channel
)的記憶體分配。new
用於各種型別的記憶體分配。
內建函式make(T, args)
與new(T)
有著不同的功能,make只能建立slice
、map
和channel
,並且返回一個有初始值(非零)的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 }
sExpr
和expr1
,expr2
,expr3
的型別必須一致.go 預設每個case後面都帶有一個 break,如果強制執行後面的的,可以使用fallthrough
,注意:如果sExpr
是一個表示式的話後面的case型別就是bool型別了,比如switch a>1{...}
3.2 迴圈
-
for迴圈:
語法
for expression1; expression2; expression3 { //... }
expression1
、expression2
和expression3
都是表示式,其中expression1
和expression3
是變數宣告或者函式呼叫返回值之類的,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(¶m) 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函式
main
和init
函式是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核數的最大值,並返回之前的值。