golang五十度灰阅读(高级篇)
五十度灰文章是之前实习时候大佬推荐的,最近才仔细的去阅读学习,这里主要是高级篇部分阅读解析
前言
网上找到golang五十度灰的英文原版,以及翻译版,但是翻译版完全是google翻译的结果,这里主要是阅读英文原版并且结合自己的理解给出解释,可以看作是翻译版,但是我四级都没过:(
其次这里只有高级篇,因为初中级的看google翻译版都没啥问题,几个难点,有时间会记录一下。
总的来说高级篇是需要你对go有一定的源码阅读或者底层理解才能更好的理解这里出现的问题,当然你记住了也没有任何问题,也是可以提高自己的代码水平。
1、Using Pointer Receiver Methods On Value Instances
如果需要使用指针变量作为方法的参数,那么调用者的地址必须是可取的
但是并不是所有的变量都是可取址的,比如Map中的元素、通过interface引用的变量
Fails:
1 | package main |
Works:
1 | func (p data) print() { |
2、Updating Map Value Fields
Map中无法更新struct的某个字段值,因为Map元素是不可取址的
Fails:
1 | package main |
Compile Error:
cannot assign to m[“x”].name
但是slice元素是可以取址的,即可修改的:
1 | package main |
修复Map中的错误有两个办法:
Works one 是采用临时变量
1 | package main |
Works two 是采用指针作为值的map
1 | package main |
思考题,下面代码会发生什么?
1 | package main |
结果是:编译成功,但是运行panic
原因是:m[“z”]的value是一个nil,所以自然就不能赋值
3、”nil” Interfaces and “nil” Interfaces Values
interface只有当类型和值都为nil时才能满足”interface{} == nil”
interface的类型和值会根据创建对应interface变量的类型和值进行变化,如果将某类型变量的零值赋予interface变量,那么interface变量并不与nil或者其零值相等
fails:
1 | package main |
当你的函数返回interface时,可能会产生下面情况:
Incorrect:
1 | package main |
Works:
1 | package main |
我认为更好的解决办法应该是将interface变量进行断言后判断,这样就不需要特别关注返回nil了,但是可能会出现断言panic,也是需要注意的问题
4、Stack and Heap Variables
在golang中,无论是new()
还是make()
申请的变量,变量所处的内存地址是由编译器决定的,编译器根据变量的大小和escap analysis
的结果来决定其位置
所以在代码中,我们可以返回局部变量的引用,但是在c或者c++中是不行的。
同时可以在go build
或者go run
上传入-m
的gc flag
,例如:go run -gcflags -m main.go
5、GOMAXPROCS, Concurrency, and Parallelism
在go1.5之前,go只有一个工作线程,在go1.5及以后,我们可以通过runtime.GOMAXPROCS()
来设置工作线程的数量,并且默认是num of cpu
即CPU核数(超线程就翻倍,超线程本质上是一个规定:如果一个cpu内核在两个线程之间切换的时间少于某个值,则大致认为两个线程是并行的)
GOMAXPROCS可以设置的值在go1.10之后不再受限制,之前在1.5之后是256,1.9是1024
当你的GOMAXPROCS的值不超过num of cpu
时,可以认为是并行的,超过之后则是并发的,这涉及到GMP的调度,简单说GOMAXPROCS的值就是go在后台启动的系统线程数量的最小值,可以参考Golang 的 协程调度机制 与 GOMAXPROCS 性能调优和也谈goroutine调度器
在五十度灰的原文中,其实只是表达了GOMAXPROCS的使用,但是我们应该了解其底层机制才能读懂。
6、Read and Write Operation Reordering
Go可能会重排某些操作,但是它会保证在同一个goroutine中的整体行为是不变的,即最后结果一致,但是它并不会保证多个goroutine的执行顺序。
1 | package main |
上面的例子多执行几次会得到不同的结果
1
23
40
20
01
4
02的结果表明b在a之前更新了,这是重排的结果,但是不影响最后的整体行为
这里主要提及的问题就是多个goroutine之间共享变量的读写顺序问题,可以通过sync
包提供的锁机制或者channel
来解决。
7、Preemptive Scheduling
在goroutine的调度中,可能存在某个goroutine一直保持运行,使得调度器无法运行,比如包含一个空的for循环的goroutine
1 | package main |
for循环可以不是空的,但是只要不包含触发调度器执行的代码,就会出现这种情况
调度器会在GC、“go“声明、channel阻塞操作、阻塞系统调度和lock操作后运行,还有一种情况是在调用非内联函数时执行,具体调度还是可以参考也谈goroutine调度器
1 | package main |
可以通过传入“-m“ gcflags来查询你在for循环中调用的函数是否是内联的,例如:go build -gcflags -m main.go
此外,如果你是单纯的计算任务,又需要调度器调度,可以使用runtime.Goshed()
方法来显示调度
1 | package main |
Cgo篇
下面是Cgo部分,严格讲与高级篇无关,对在go中调用c无需求的可以跳过
8、Import C and Multiline Import Blocks
在Go中使用C需要用到Cgo,可以通过import “C“
实现导入C中的包
1 | package main |
你也可以采用import块来导入,但是导入块中不能包含其他包
1 | package main |
Compile Error:
./main.go:13:2: could not determine kind of name for C.free
9、No blank lines Between Import C and Cgo Comments
在导入C包上方的注释必须与import
语句无空格,否则会编译报错
1 | package main |
Compile Error:
./main.go:15:2: could not determine kind of name for C.free
10、Can’t Call C Functions with Variable Arguments
你不能直接调用带有不定数量参数的c函数
1 | package main |
Compile Error:
./main.go:15:2: unexpected type: …
你可以将其包裹在已知参数数量的函数中调用
1 | package main |