Golang开发新手常犯的50个错误
《50 Shades of Go: Traps, Gotchas, and Common Mistakes for New Golang Devs》
原文地址:http://devs.cloudimmunity.com/gotchas-and-common-mistakes-in-go-golang/index.html
一、初級
1、不允許左大括號單獨一行
2、不允許出現未使用的變量
3、不允許出現未使用的import
解決方法:使用_作為引入包別名
package mainimport ( _ "fmt" // 指定別名為`_`"log""time" )var _ = log.Println // 變量名為`_`func main() { _ = time.Now }4、短的變量聲明(Short Variable Declarations)只能在函數內部使用
package main// myvar := 1 // error var myvar = 1 // okfunc main() { }5、不能使用短變量聲明(Short Variable Declarations)重復聲明
6、不能使用短變量聲明(Short Variable Declarations)這種方式來設置字段值
package mainfunc main() { one := 0// one := 1 //error: Redeclaring Variables Using Short Variable Declarations// data.result, err := work() //error:Can't Use Short Variable Declarations to Set Field Valuesdata.result, err = work() //ok }7、意外的變量幽靈(Accidental Variable Shadowing)
短變量聲明語法,很好用,但是代碼塊中使用短變更聲明與外部相同的變量時, 
 沒有語法編譯錯誤,但是代碼塊中同名短變量聲明從聲明開始到代碼塊結束,對變量的修改將不會影響到外部變量!
這種現象稱之為幽靈變量,可以使用go tool vet -shadow you_file.go檢查幽靈變量。 
 使用go-ynet命令會執行更多幽靈變量的檢測。
8、不能使用nil初始化一個未指定類型的變量
// var x = nil //error var x interface{} = nil // OK _ = x9、不能直接使用nil值的Slice和Map
10、map使用make分配內存時可指定capicity,但是不能對map使用cap函數
11、字符串不允許使用nil值
在golang中,nil只能賦值給指針、channel、func、interface、map或slice類型的變量。
var x string = nil //error if x == nil { //errorx = "default" }//var x string //defaults to "" (zero value) if x == "" {x = "default" }12、數組用于函數傳參時是值復制
注意:方法或函數調用時,傳入參數都是值復制(跟賦值一致),除非是map、slice、channel、指針類型這些特殊類型是引用傳遞。
x := [3]int{1,2,3}// 數組在函數中傳參是值復制 func(arr [3]int) {arr[0] = 7fmt.Println(arr) //prints [7 2 3] }(x) fmt.Println(x) //prints [1 2 3] (not ok if you need [7 2 3])// 使用數組指針實現引用傳參 func(arr *[3]int) {(*arr)[0] = 7fmt.Println(arr) //prints &[7 2 3] }(&x) fmt.Println(x) //prints [7 2 3]13、range關鍵字返回是鍵值對,而不是值
x := []string{"a","b","c"}// for v := range x { // fmt.Println(v) //prints 0, 1, 2 // }for _, v := range x {fmt.Println(v) //prints a, b, c }14、Slice和Array是一維的
Go表面上看起來像多維的,實際上只是一維的。但可以使用原始的一維數組、一維的切片來實現多維。
15、從不存在key的map中取值時,返回的總是”0值”
x := map[string] string {"one":"1", "two":"2"} if _,ok := x["two"]; !ok { // 判斷是否存在,x[key]始終有返回值}16、字符串是不可變
x := "text" // x[0] = 'T' // errorxtytes := []byte(x) xbytes[0] = 'T' // okfmt.Println(string(xbytes)) //prints Text17、字符串與[]byte之間的轉換是復制(有內存損耗),可以用map[string] []byte建立字符串與[]byte之間映射,也可range來避免內存分配來提高性能
//[]byte: for i,v := range []byte(str) { }18、string的索引操作返回的是byte(或uint8),如想獲取字符可使用for range,也可使用unicode/utf8包和golang.org/x/exp/utf8string包的At()方法。
19、字符串并不總是UTF8的文本
20、len(str)返回的是字符串的字節數,獲取字符串的rune數是使用unicode/utf8.RuneCountInString()函數,但注意一些字符也可能是由多個rune組成,如e?是兩個rune組成。
21、在Slice、Array、Map的多行書寫最后的逗號不可省略
x := []int{ 1,// 2 //error 3, // ok }y := []int {1, 2, 3,} // ok z := []int {1, 2, 3} // 單行書寫,最后一個元素的逗號可省略22、內置數據結構的操作并不同步,但可把Go提供了并發的特性使用起來:goroutines和channels。
23、使用for range迭代String,是以rune來迭代的。
一個字符,也可以有多個rune組成。需要處理字符,盡量使用golang.org/x/text/unicode/norm包。
for range總是嘗試將字符串解析成utf8的文本,對于它無法解析的字節,它會返回oxfffd的rune字符。 
 因此,任何包含非utf8的文本,一定要先將其轉換成字符切片([]byte)。
24、使用for range迭代map時每次迭代的順序可能不一樣,因為map的迭代是隨機的。
25、switch的case默認匹配規則不同于其它語言的是,匹配case條件后默認退出,除非使用fallthrough繼續匹配;而其它語言是默認繼續匹配,除非使用break退出匹配。
26、只有后置自增、后置自減,不存在前置自增、前置自減
27、位運算的非操作是^(跟異或位運算一樣),有別于其它語言的~。
28、位運算(與、或、異或、取反)優先級高于四則運算(加、減、乘、除、取余),有別于C語言。
29、結構體在序列化時非導出字段(以小寫字母開頭的字段名)不會被encode,因此在decode時這些非導出字段的值為”0值”
30、程序不等所有goroutine結束就會退出。可通過channel實現主協程(main goroutine)等待所有goroutine完成。
31、對于無緩存區的channel,寫入channel的goroutine會阻塞直到被讀取,讀取channel的goroutine會阻塞直到有數據寫入。
32、從一個closed狀態的channel讀取數據是安全的,可通過返回狀態(第二個返回參數)判斷是否關閉;而向一個closed狀態的channel寫數據會導致panic。
33、向一個nil值(未用make分配空間)的channel發送或讀取數據,會導致永遠阻塞。
package mainimport ( "fmt""time" )func main() { var ch chan intfor i := 0; i < 3; i++ {go func(idx int) {ch <- (idx + 1) * 2}(i)}//get first resultfmt.Println("result:",<-ch)//do other worktime.Sleep(2 * time.Second) }34、方法接收者是類型(T),接收者只是原對象的值復制,在方法中修改接收者不會修改原始對象的值;如果方法接收者是指針類型(*T),是對原對象的引用,方法中對其修改當然是原對象修改。
type data struct { num intkey *stringitems map[string]bool }func (this *data) pmethod() { this.num = 7 }func (this data) vmethod() { this.num = 8*this.key = "v.key"this.items["vmethod"] = true }35、log包中的log.Fatal和log.Panic不僅僅記錄日志,還會中止程序。它不同于Logging庫。
二、中級
1、關閉HTTP的Response.Body
使用defer語句關閉資源時要注意nil值,在defer語句之前要進行nill值處理
以下以http包的使用為例
package mainimport ( "fmt""net/http""io/ioutil" )func main() { resp, err := http.Get("https://api.ipify.org?format=json")if resp != nil {defer resp.Body.Close() // ok,即使不讀取Body中的數據,即使Body是空的,也要調用close方法}//defer resp.Body.Close() // (1)Error:在nil值判斷之前使用,resp為nil時defer中的語句執行會引發空引用的panicif err != nil {fmt.Println(err)return}//defer resp.Body.Close() // (2)Error:排除了nil隱患,但是出現重定向錯誤時,仍然需要調用closebody, err := ioutil.ReadAll(resp.Body)if err != nil {fmt.Println(err)return}fmt.Println(string(body)) }在Go 1.5之前resp.Body.Close()會讀取并丟失body中的數據,保證在啟用keepaliva的http時能夠在下一次請求時重用。 
 在Go 1.5之后,就需要在關閉前手動處理。
如果只是讀取Body的部分,就很有必要在關閉Body之前做這種手動處理。例如處理json api響應時json.NewDecoder(resp.Body).Decode(&data)就需要處理掉剩余的數據。
2、關閉HTTP連接:
(1) 可使用req.Close=true,表示在http請求完成時關閉連接
(2) 添加Connection: close的連接請求頭。http服務端也會發送Connection: close的響應頭,http庫處理響應時會關閉連接。
package mainimport ( "fmt""net/http""io/ioutil" )func main() { req, err := http.NewRequest("GET","http://golang.org",nil)if err != nil {fmt.Println(err)return}req.Close = true//or do this://req.Header.Add("Connection", "close")resp, err := http.DefaultClient.Do(req)if resp != nil {defer resp.Body.Close()}if err != nil {fmt.Println(err)return}body, err := ioutil.ReadAll(resp.Body)if err != nil {fmt.Println(err)return}fmt.Println(len(string(body))) }(3)全局關閉http連接重用。
對于相同http server發送多個請求時,適合保持網絡連接; 
 但對于短時間內向多個HTTP服務器發送一個或兩個請求時,最好在每次接收到服務端響應后關閉網絡鏈接
3、Json反序列化數字到interface{}類型的值中,默認解析為float64類型,在使用時要注意。
var data = []byte(`{"status": 200}`)var result map[string]interface{}if err := json.Unmarshal(data, &result); err != nil {fmt.Println("error:", err)return}//var status = result["status"].(int) //error: panic: interface conversion: interface is float64, not int var status = uint64(result["status"].(float64)) //okfmt.Println("status value:",status)(1) 使用Decoder類型解析JSON
var data = []byte(`{"status": 200}`)var decoder = json.NewDecoder(bytes.NewReader(data)) decoder.UseNumber()if err := decoder.Decode(&result); err != nil {fmt.Println("error:", err)return }var status,_ = result["status"].(json.Number).Int64() //ok fmt.Println("status value:",status)var status2 uint64 if err := json.Unmarshal([]byte(result["status"].(json.Number).String()), &status2); err != nil {fmt.Println("error:", err)return }(2)使用struct結構體映射
var data = []byte(`{"status": 200}`) var result struct {Status uint64 `json:"status"` }if err := json.NewDecoder(bytes.NewReader(data)).Decode(&result); err != nil {fmt.Println("error:", err)return }fmt.Printf("result => %+v",result) //prints: result => {Status:200}(3) 使用struct映射數字為json.RawMessage
records := [][]byte{[]byte(`{"status": 200, "tag":"one"}`),[]byte(`{"status":"ok", "tag":"two"}`),}for idx, record := range records {var result struct {StatusCode uint64StatusName stringStatus json.RawMessage `json:"status"`Tag string `json:"tag"`}if err := json.NewDecoder(bytes.NewReader(record)).Decode(&result); err != nil {fmt.Println("error:", err)return}var sstatus stringif err := json.Unmarshal(result.Status, &sstatus); err == nil {result.StatusName = sstatus}var nstatus uint64if err := json.Unmarshal(result.Status, &nstatus); err == nil {result.StatusCode = nstatus}fmt.Printf("[%v] result => %+v\n",idx,result)}4、Struct、Array、Slice、Map的比較
如果struct結構體的所有字段都能夠使用==操作比較,那么結構體變量也能夠使用==比較。 
 但是,如果struct字段不能使用==比較,那么結構體變量使用==比較會導致編譯錯誤。
同樣,array只有在它的每個元素能夠使用==比較時,array變量才能夠比較。
Go提供了一些用于比較不能直接使用==比較的函數,其中最常用的是reflect.DeepEqual()函數。
DeepEqual()函數對于nil值的slice與空元素的slice是不相等的,這點不同于bytes.Equal()函數。
type data struct { num intfp float32complex complex64str stringchar runeyes boolevents <-chan stringhandler interface{}ref *byteraw [10]byte }v1 := data{} v2 := data{} fmt.Println("v1 == v2:",v1 == v2) //prints: v1 == v2: truetype data2 struct { num int //okchecks [10]func() bool //not comparabledoit func() bool //not comparablem map[string] string //not comparablebytes []byte //not comparable }v3 := data2{} v4 := data2{} fmt.Println("v3 == v4:",v3 == v4) // errorv5 := data2{} v6 := data2{} fmt.Println("v5 == v6:",reflect.DeepEqual(v5,v6)) //prints: v5 == v6: truem1 := map[string]string{"one": "a","two": "b"} m2 := map[string]string{"two": "b", "one": "a"} fmt.Println("m1 == m2:",reflect.DeepEqual(m1, m2)) //prints: m1 == m2: trues1 := []int{1, 2, 3} s2 := []int{1, 2, 3} fmt.Println("s1 == s2:",reflect.DeepEqual(s1, s2)) //prints: s1 == s2: truevar b1 []byte = nil b2 := []byte{} fmt.Println("b1 == b2:",reflect.DeepEqual(b1, b2)) //prints: b1 == b2: falsevar b3 []byte = nil b4 := []byte{} fmt.Println("b3 == b4:",bytes.Equal(b3, b4)) //prints: b3 == b4: trueDeepEqual()函數并不總是能夠正確處理slice。
var str string = "one" var in interface{} = "one" fmt.Println("str == in:",str == in,reflect.DeepEqual(str, in)) //prints: str == in: true truev1 := []string{"one","two"} v2 := []interface{}{"one","two"} fmt.Println("v1 == v2:",reflect.DeepEqual(v1, v2)) //prints: v1 == v2: false (not ok)data := map[string]interface{}{"code": 200,"value": []string{"one","two"}, } encoded, _ := json.Marshal(data) var decoded map[string]interface{} json.Unmarshal(encoded, &decoded) fmt.Println("data == decoded:",reflect.DeepEqual(data, decoded)) //prints: data == decoded: false (not ok)如果要忽略大小寫來比較包含文字數據的字節切片(byte slice), 
 不建議使用bytes包和strings包里的ToUpper()、ToLower()這些函數轉換后再用==、byte.Equal()、bytes.Compare()等比較,
ToUpper()、ToLower()只能處理英文文字,對其它語言無效。因此建議使用strings.EqualFold()和bytes.EqualFold()
如果要比較用于驗證用戶數據密鑰信息的字節切片時,使用reflact.DeepEqual()、bytes.Equal()、 
 bytes.Compare()會使應用程序遭受計時攻擊(Timing Attack),可使用crypto/subtle.ConstantTimeCompare()避免泄漏時間信息。
5、從panic中恢復
recover()函數能夠捕獲或攔截panic,但必須在defer函數或語句中直接調用,否則無效。
package mainimport "fmt"func doRecover() { fmt.Println("recovered =>",recover()) //prints: recovered => <nil> }func main() { defer func() {fmt.Println("recovered:",recover()) // ok}()defer func() {doRecover() //panic is not recovered}()// recover() //doesn't do anythingpanic("not good")// recover() //won't be executed :)fmt.Println("ok") }6、在slice、array、map的for range子句中修改和引用數據項
使用range獲取的數據項是從集合元素的復制過來的,并非引用原始數據,但使用索引能訪問原始數據。
data := []int{1,2,3} for _,v := range data {v *= 10 // original item is not changed }data2 := []int{1,2,3} for i,v := range data2 {data2[i] *= 10 // change original item }// 元素是指針類型就不一樣了 data3 := []*struct{num int} {{1}, {2}, {3}} for _,v := range data {v.num *= 10 }fmt.Println("data:", data) //prints data: [1 2 3] fmt.Println("data:", data2) //prints data: [10 20 30] fmt.Println(data3[0],data3[1],data3[2]) //prints &{10} &{20} &{30}7、Slice中的隱藏數據
從一個slice上再生成一個切片slice,新的slice將直接引用原始slice的那個數組,兩個slice對同一數組的操作,會相互影響。
可通過為新切片slice重新分配空間,從slice中copy部分的數據來避免相互之間的影響。
raw := make([]byte,10000) fmt.Println(len(raw),cap(raw),&raw[0]) //prints: 10000 10000 <byte_addr_x>data := raw[:3] fmt.Println(len(data),cap(data),&data[0]) //prints: 3 10000 <byte_addr_x>res := make([]byte,3) copy(res,raw[:3]) fmt.Println(len(res),cap(res),&res[0]) //prints: 3 3 <byte_addr_y>8、Slice超范圍數據覆蓋
從已存在的切片slice中繼續切片時,新切片的capicity等于原capicity減去新切片之前部分的數量,新切片與原切片都指向同一數組空間。
新生成切片之間capicity區域是重疊的,因此在添加數據時易造成數據覆蓋問題。
slice使用append添加的內容時超出capicity時,會重新分配空間。 
 利用這一點,將要修改的切片指定capicity為切片當前length,可避免切片之間的超范圍覆蓋影響。
9、Slice增加元素重新分配內存導致的怪事
slice在添加元素前,與其它切片共享同一數據區域,修改會相互影響;但添加元素導致內存重新分配之后,不再指向原來的數據區域,修改元素,不再影響其它切片。
s1 := []int{1,2,3}fmt.Println(len(s1),cap(s1),s1) //prints 3 3 [1 2 3]s2 := s1[1:]fmt.Println(len(s2),cap(s2),s2) //prints 2 2 [2 3]for i := range s2 { s2[i] += 20 }//still referencing the same arrayfmt.Println(s1) //prints [1 22 23]fmt.Println(s2) //prints [22 23]s2 = append(s2,4)for i := range s2 { s2[i] += 10 }//s1 is now "stale"fmt.Println(s1) //prints [1 22 23]fmt.Println(s2) //prints [32 33 14]10、類型重定義與方法繼承
從一個已存在的(non-interface)非接口類型重新定義一個新類型時,不會繼承原類型的任何方法。 
 可以通過定義一個組合匿名變量的類型,來實現對此匿名變量類型的繼承。
但是從一個已存在接口重新定義一個新接口時,新接口會繼承原接口所有方法。
11、從”for switch”和”for select”代碼塊中跳出。
無label的break只會跳出最內層的switch/select代碼塊。 
 如需要從switch/select代碼塊中跳出外層的for循環,可以在for循環外部定義label,供break跳出。
return當然也是可以的,如果在這里可以用的話。
12、在for語句的閉包中使用迭代變量會有問題
在for迭代過程中,迭代變量會一直保留,只是每次迭代值不一樣。 
 因此在for循環中在閉包里直接引用迭代變量,在執行時直接取迭代變量的值,而不是閉包所在迭代的變量值。
如果閉包要取所在迭代變量的值,就需要for中定義一個變量來保存所在迭代的值,或者通過閉包函數傳參。
package mainimport ( "fmt""time" )func forState1(){data := []string{"one","two","three"}for _,v := range data {go func() {fmt.Println(v)}()}time.Sleep(3 * time.Second) //goroutines print: three, three, threefor _,v := range data {vcopy := v // 使用臨時變量go func() {fmt.Println(vcopy)}()}time.Sleep(3 * time.Second) //goroutines print: one, two, threefor _,v := range data {go func(in string) {fmt.Println(in)}(v)}time.Sleep(3 * time.Second) //goroutines print: one, two, three }func main() { forState1() }再看一個坑埋得比較深的例子。
package mainimport ( "fmt""time" )type field struct { name string }func (p *field) print() { fmt.Println(p.name) }func main() { data := []field{{"one"},{"two"},{"three"}}for _,v := range data {// 解決辦法:添加如下語句// v := vgo v.print()}time.Sleep(3 * time.Second) //goroutines print: three, three, threedata2 := []*field{{"one"}, {"two"}, {"three"}} // 注意data2是指針數組for _, v := range data2 {go v.print() // go執行是函數,函數執行之前,函數的接受對象已經傳過來}time.Sleep(3 * time.Second) //goroutines print: one, two, three }13、defer函數調用參數
defer后面必須是函數或方法的調用語句。defer后面不論是函數還是方法,輸入參數的值是在defer聲明時已計算好, 
 而不是調用開始計算。
要特別注意的是,defer后面是方法調用語句時,方法的接受者是在defer語句執行時傳遞的,而不是defer聲明時傳入的。
type field struct{num int } func(t *field) print(n int){fmt.println(t.num, n) } func main() { var i int = 1defer fmt.Println("result2 =>",func() int { return i * 2 }())i++v := field{1}defer v.print(func() int { return i * 2 }())v = field{2}i++// prints: // 2 4// result => 2 (not ok if you expected 4) }14、defer語句調用是在當前函數結束之后調用,而不是變量的作用范圍。
for _,target := range targets {f, err := os.Open(target)if err != nil {fmt.Println("bad target:",target,"error:",err) //prints error: too many open filesbreak}defer f.Close() //will not be closed at the end of this code block, but closed at end of this function// 解決方法1:不用defer// f.Close()// 解決方法2:將for中的語句添加到匿名函數中執行。// func () {// }() }15、失敗的類型斷言
var.(T)類型斷言失敗時會返回T類型的“0值”,而不是變量原始值。
var data interface{} = "great"if data, ok := data.(int); ok {fmt.Println("[is an int] value =>",data)} else {fmt.Println("[not an int] value =>",data) //prints: [not an int] value => 0 (not "great")}if res, ok := data.(int); ok {fmt.Println("[is an int] value =>",res)} else {fmt.Println("[not an int] value =>",data) //prints: [not an int] value => great (as expected)}16、阻塞的goroutine與資源泄漏
func First(query string, replicas ...Search) Result { c := make(chan Result)// 解決1:使用緩沖的channel: c := make(chan Result,len(replicas))searchReplica := func(i int) { c <- replicas[i](query) }// 解決2:使用select-default,防止阻塞// searchReplica := func(i int) { // select {// case c <- replicas[i](query):// default:// }// }// 解決3:使用特殊的channel來中斷原有工作// done := make(chan struct{})// defer close(done)// searchReplica := func(i int) { // select {// case c <- replicas[i](query):// case <- done:// }// }for i := range replicas {go searchReplica(i)}return <-c }三、高級
1、用值實例上調用接收者為指針的方法
對于可尋址(addressable)的值變量(而不是指針),可以直接調用接受對象為指針類型的方法。 
 換句話說,就不需要為可尋址值變量定義以接受對象為值類型的方法了。
但是,并不是所有變量都是可尋址的,像Map的元素就是不可尋址的。
package mainimport "fmt"type data struct { name string }func (p *data) print() { fmt.Println("name:",p.name) }type printer interface { print() }func main() { d1 := data{"one"}d1.print() //ok// var in printer = data{"two"} //errorvar in printer = &data{"two"}in.print()m := map[string]data {"x":data{"three"}}//m["x"].print() //errord2 = m["x"]d2.print() // ok }2、更新map值的字段
如果map的值類型是結構體類型,那么不能更新從map中取出的結構體的字段值。 
 但是對于結構體類型的slice卻是可以的。
3、nil值的interface{}不等于nil
在golang中,nil只能賦值給指針、channel、func、interface、map或slice類型的變量。
interface{}表示任意類型,可以接收任意類型的值。interface{}變量在底是由類型和值兩部分組成,表示為(T,V),interface{}變量比較特殊,判斷它是nil時,要求它的類型和值都是nil,即(nil, nil)。 
 其它類型變量,只要值是nil,那么此變量就是nil(為什么?變量類型不是nil,那當然只能用值來判斷了)
聲明變量interface{},它默認就是nil,底層類型與值表示是(nil, nil)。 
 當任何類型T的變量值V給interface{}變量賦值時,interface{}變量的底層表示是(T, V)。只要T非nil,即使V是nil,interface{}變量也不是nil。
因此,var a interface{}、var a interface{} = nil、“
var data *bytevar in interface{}fmt.Println(data,data == nil) //prints: <nil> truefmt.Println(in,in == nil) //prints: <nil> truein = datafmt.Println(in,in == nil) //prints: <nil> false//'data' is 'nil', but 'in' is not 'nil'doit := func(arg int) interface{} {var result *struct{} = nilif(arg > 0) {result = &struct{}{}}return result}if res := doit(-1); res != nil {fmt.Println("good result:",res) //prints: good result: <nil>//'res' is not 'nil', but its value is 'nil'}doit = func(arg int) interface{} {var result *struct{} = nilif(arg > 0) {result = &struct{}{}} else {return nil //return an explicit 'nil'}return result}if res := doit(-1); res != nil {fmt.Println("good result:",res)} else {fmt.Println("bad result (res is nil)") //here as expected}4、變量內存的分配
在C++中使用new操作符總是在heap上分配變量。Go編譯器使用new()和make()分配內存的位置到底是stack還是heap, 
 取決于變量的大小(size)和逃逸分析的結果(result of “escape analysis”)。這意味著Go語言中,返回本地變量的引用也不會有問題。
要想知道變量內存分配的位置,可以在go build、go run命令指定-gcflags -m即可: 
 go run -gcflags -m app.go
5、GOMAXPROCS、Concurrency和Parallelism
Go 1.4及以下版本每個操作系統線程只使用一個執行上下文execution context)。這意味著每個時間片,只有一個goroutine執行。 
 從Go 1.5開始可以設置執行上下文的數量為CUP內核數量runtime.NumCPU(),也可以通過GOMAXPROCS環境變量來設置, 
 還可調用runtime.GOMAXPROCS()函數來設置。
注意,GOMAXPROCS并不代表Go運行時能夠使用的CPU數量,它是一個小256的數值,可以設置比實際的CPU數量更大的數字。
fmt.Println(runtime.GOMAXPROCS(-1)) //prints: X (1 on play.golang.org)fmt.Println(runtime.NumCPU()) //prints: X (1 on play.golang.org)runtime.GOMAXPROCS(20)fmt.Println(runtime.GOMAXPROCS(-1)) //prints: 20runtime.GOMAXPROCS(300)fmt.Println(runtime.GOMAXPROCS(-1)) //prints: 2566、讀寫操作排序
Go可能會對一些操作排序,但它保證在goroutine的所有行為保持不變。 
 但是,它無法保證在跨多個goroutine時的執行順序。
看到上面顯示的02,14這樣的值了嗎?這沒什么奇怪的。以02這樣的輸出結果,goroutine的執行應該是這樣的
-------------------------------------p函數 | u1函數 | u2函數 | ------------------------------------- println(a) | | | | a = 1 | | | b = 2 | | println(b) | | | | | a = 3 | | | b = 4 | ------------------------------------- (從上向下為時間執行順序)7、優先調度
有一些比較流氓的goroutine會阻止其它goroutine的執行。 
 例如for循環可能就不允許調度器(scheduler)執行。
scheduler會在GC、go語句、阻塞channel的操作、阻塞系統調用、lock操作等語句執行之后立即執行。 
 也可以顯示地執行runtime.Gosched()(讓出時間片)使scheduler執行調度工作。
總結
以上是生活随笔為你收集整理的Golang开发新手常犯的50个错误的全部內容,希望文章能夠幫你解決所遇到的問題。
                            
                        - 上一篇: android 输入法 确定按钮,And
 - 下一篇: 凡人修仙学指针-1