[TOC]
# 使用指針作為方法的 receiver
只要值是可尋址的,就可以在值上直接調用指針方法。即是對一個方法,它的 receiver 是指針就足矣。
但不是所有值都是可尋址的,比如 map 類型的元素、通過 interface 引用的變量:
~~~
type data struct {
name string
}
type printer interface {
print()
}
func (p *data) print() {
fmt.Println("name: ", p.name)
}
func main() {
d1 := data{"one"}
d1.print() // d1 變量可尋址,可直接調用指針 receiver 的方法
var in printer = data{"two"}
in.print() // 類型不匹配
m := map[string]data{
"x": data{"three"},
}
m["x"].print() // m["x"] 是不可尋址的 // 變動頻繁
}
~~~
> cannot use data literal (type data) as type printer in assignment:
>
> data does not implement printer (print method has pointer receiver)
>
> cannot call pointer method on m\["x"\]
> cannot take the address of m\["x"\]
# 更新 map 字段的值
如果 map 一個字段的值是 struct 類型,則無法直接更新該 struct 的單個字段:
~~~
// 無法直接更新 struct 的字段值
type data struct {
name string
}
func main() {
m := map[string]data{
"x": {"Tom"},
}
m["x"].name = "Jerry"
}
~~~
> cannot assign to struct field m\["x"\].name in map
因為 map 中的元素是不可尋址的。需區分開的是,slice 的元素可尋址:
~~~
type data struct {
name string
}
func main() {
s := []data{{"Tom"}}
s[0].name = "Jerry"
fmt.Println(s) // [{Jerry}]
}
~~~
注意:不久前 gccgo 編譯器可更新 map struct 元素的字段值,不過很快便修復了,官方認為是 Go1.3 的潛在特性,無需及時實現,依舊在 todo list 中。
更新 map 中 struct 元素的字段值,有 2 個方法:
* 使用局部變量
~~~
// 提取整個 struct 到局部變量中,修改字段值后再整個賦值
type data struct {
name string
}
func main() {
m := map[string]data{
"x": {"Tom"},
}
r := m["x"]
r.name = "Jerry"
m["x"] = r
fmt.Println(m) // map[x:{Jerry}]
}
~~~
* 使用指向元素的 map 指針
~~~
func main() {
m := map[string]*data{
"x": {"Tom"},
}
m["x"].name = "Jerry" // 直接修改 m["x"] 中的字段
fmt.Println(m["x"]) // &{Jerry}
}
~~~
但是要注意下邊這種誤用:
~~~
func main() {
m := map[string]*data{
"x": {"Tom"},
}
m["z"].name = "what???"
fmt.Println(m["x"])
}
~~~
> panic: runtime error: invalid memory address or nil pointer dereference
# nil interface 和 nil interface 值
雖然 interface 看起來像指針類型,但它不是。interface 類型的變量只有在類型和值均為 nil 時才為 nil
如果你的 interface 變量的值是跟隨其他變量變化的(霧),與 nil 比較相等時小心:
~~~
func main() {
var data *byte
var in interface{}
fmt.Println(data, data == nil) // <nil> true
fmt.Println(in, in == nil) // <nil> true
in = data
fmt.Println(in, in == nil) // <nil> false // data 值為 nil,但 in 值不為 nil
}
~~~
如果你的函數返回值類型是 interface,更要小心這個坑:
~~~
// 錯誤示例
func main() {
doIt := func(arg int) interface{} {
var result *struct{} = nil
if arg > 0 {
result = &struct{}{}
}
return result
}
if res := doIt(-1); res != nil {
fmt.Println("Good result: ", res) // Good result: <nil>
fmt.Printf("%T\n", res) // *struct {} // res 不是 nil,它的值為 nil
fmt.Printf("%v\n", res) // <nil>
}
}
// 正確示例
func main() {
doIt := func(arg int) interface{} {
var result *struct{} = nil
if arg > 0 {
result = &struct{}{}
} else {
return nil // 明確指明返回 nil
}
return result
}
if res := doIt(-1); res != nil {
fmt.Println("Good result: ", res)
} else {
fmt.Println("Bad result: ", res) // Bad result: <nil>
}
}
~~~
# 堆棧變量
你并不總是清楚你的變量是分配到了堆還是棧。
在 C++ 中使用`new`創建的變量總是分配到堆內存上的,但在 Go 中即使使用`new()`、`make()`來創建變量,變量為內存分配位置依舊歸 Go 編譯器管。
Go 編譯器會根據變量的大小及其 "escape analysis" 的結果來決定變量的存儲位置,故能準確返回本地變量的地址,這在 C/C++ 中是不行的。
在 go build 或 go run 時,加入 -m 參數,能準確分析程序的變量分配位置:

# GOMAXPROCS、Concurrency(并發)and Parallelism(并行)
Go 1.4 及以下版本,程序只會使用 1 個執行上下文 / OS 線程,即任何時間都最多只有 1 個 goroutine 在執行。
Go 1.5 版本將可執行上下文的數量設置為`runtime.NumCPU()`返回的邏輯 CPU 核心數,這個數與系統實際總的 CPU 邏輯核心數是否一致,取決于你的 CPU 分配給程序的核心數,可以使用`GOMAXPROCS`環境變量或者動態的使用`runtime.GOMAXPROCS()`來調整。
誤區:`GOMAXPROCS`表示執行 goroutine 的 CPU 核心數,參考[文檔](https://golang.org/pkg/runtime/)
`GOMAXPROCS`的值是可以超過 CPU 的實際數量的,在 1.5 中最大為 256
~~~
func main() {
fmt.Println(runtime.GOMAXPROCS(-1)) // 4
fmt.Println(runtime.NumCPU()) // 4
runtime.GOMAXPROCS(20)
fmt.Println(runtime.GOMAXPROCS(-1)) // 20
runtime.GOMAXPROCS(300)
fmt.Println(runtime.GOMAXPROCS(-1)) // Go 1.9.2 // 300
}
~~~
# 讀寫操作的重新排序
Go 可能會重排一些操作的執行順序,可以保證在一個 goroutine 中操作是順序執行的,但不保證多 goroutine 的執行順序
~~~
var _ = runtime.GOMAXPROCS(3)
var a, b int
func u1() {
a = 1
b = 2
}
func u2() {
a = 3
b = 4
}
func p() {
println(a)
println(b)
}
func main() {
go u1() // 多個 goroutine 的執行順序不定
go u2()
go p()
time.Sleep(1 * time.Second)
}
~~~

如果你想保持多 goroutine 像代碼中的那樣順序執行,可以使用 channel 或 sync 包中的鎖機制等。
# 優先調度
你的程序可能出現一個 goroutine 在運行時阻止了其他 goroutine 的運行,比如程序中有一個不讓調度器運行的`for`循環:
~~~
func main() {
done := false
go func() {
done = true
}()
for !done {
}
println("done !")
}
~~~
`for`的循環體不必為空,但如果代碼不會觸發調度器執行,將出現問題。
調度器會在 GC、Go 聲明、阻塞 channel、阻塞系統調用和鎖操作后再執行,也會在非內聯函數調用時執行:
~~~
func main() {
done := false
go func() {
done = true
}()
for !done {
println("not done !") // 并不內聯執行
}
println("done !")
}
~~~
可以添加`-m`參數來分析`for`代碼塊中調用的內聯函數:

你也可以使用 runtime 包中的`Gosched()`來 手動啟動調度器:
~~~
func main() {
done := false
go func() {
done = true
}()
for !done {
runtime.Gosched()
}
println("done !")
}
~~~
運行效果:

- 基礎
- 簡介
- 主要特征
- 變量和常量
- 編碼轉換
- 數組
- byte與rune
- big
- sort接口
- 和mysql類型對應
- 函數
- 閉包
- 工作區
- 復合類型
- 指針
- 切片
- map
- 結構體
- sync.Map
- 隨機數
- 面向對象
- 匿名組合
- 方法
- 接口
- 權限
- 類型查詢
- 異常處理
- error
- panic
- recover
- 自定義錯誤
- 字符串處理
- 正則表達式
- json
- 文件操作
- os
- 文件讀寫
- 目錄
- bufio
- ioutil
- gob
- 棧幀的內存布局
- shell
- 時間處理
- time詳情
- time使用
- new和make的區別
- container
- list
- heap
- ring
- 測試
- 單元測試
- Mock依賴
- delve
- 命令
- TestMain
- path和filepath包
- log日志
- 反射
- 詳解
- plugin包
- 信號
- goto
- 協程
- 簡介
- 創建
- 協程退出
- runtime
- channel
- select
- 死鎖
- 互斥鎖
- 讀寫鎖
- 條件變量
- 嵌套
- 計算單個協程占用內存
- 執行規則
- 原子操作
- WaitGroup
- 定時器
- 對象池
- sync.once
- 網絡編程
- 分層模型
- socket
- tcp
- udp
- 服務端
- 客戶端
- 并發服務器
- Http
- 簡介
- http服務器
- http客戶端
- 爬蟲
- 平滑重啟
- context
- httptest
- 優雅中止
- web服務平滑重啟
- beego
- 安裝
- 路由器
- orm
- 單表增刪改查
- 多級表
- orm使用
- 高級查詢
- 關系查詢
- SQL查詢
- 元數據二次定義
- 控制器
- 參數解析
- 過濾器
- 數據輸出
- 表單數據驗證
- 錯誤處理
- 日志
- 模塊
- cache
- task
- 調試模塊
- config
- 部署
- 一些包
- gjson
- goredis
- collection
- sjson
- redigo
- aliyunoss
- 密碼
- 對稱加密
- 非對稱加密
- 單向散列函數
- 消息認證
- 數字簽名
- mysql優化
- 常見錯誤
- go run的錯誤
- 新手常見錯誤
- 中級錯誤
- 高級錯誤
- 常用工具
- 協程-泄露
- go env
- gometalinter代碼檢查
- go build
- go clean
- go test
- 包管理器
- go mod
- gopm
- go fmt
- pprof
- 提高編譯
- go get
- 代理
- 其他的知識
- go內存對齊
- 細節總結
- nginx路由匹配
- 一些博客
- redis為什么快
- cpu高速緩存
- 常用命令
- Go 永久阻塞的方法
- 常用技巧
- 密碼加密解密
- for 循環迭代變量
- 備注
- 垃圾回收
- 協程和纖程
- tar-gz
- 紅包算法
- 解決golang.org/x 下載失敗
- 逃逸分析
- docker
- 鏡像
- 容器
- 數據卷
- 網絡管理
- 網絡模式
- dockerfile
- docker-composer
- 微服務
- protoBuf
- GRPC
- tls
- consul
- micro
- crontab
- shell調用
- gorhill/cronexpr
- raft
- go操作etcd
- mongodb