首先,无意进行语言之争,毕竟,PHP是世界上最好的语言,没有之一。这个话题可以停下来了。
2016年已经过去,16年的年度语言给了go语言
,而正好这一年我都是用go用得比较多,而且版本从1.2一直用到了1.8,有一些感受,来说说我对这个年度编程语言的一些粗浅理解吧。之前也写过一篇go语言的文章,但是那时候用得还不是很多,有些特性没有用上,所以理解上和今天的有些不同。
这篇文章就不分什么优势和劣势了,想到哪里说到哪里。
指针还是很重要
先看一个小坑,可能很多初次接触go的会遇到,go的range迭代用得也很多,下面这个例子不知道你之前遇到过没有,其实值是不会变的,还是1,2,3。
type a struct { b int } func main() { m := make([]a,0) m = append(m,a{b: 0,c: 0}) m = append(m,a{b: 1,c: 1}) m = append(m,a{b: 2,c: 2}) for _,e := range m { e.b = 9 } for _,x := range m { fmt.Printf("%v\n",x.b) } }
在range中,后面那个元素是值传递,这个很关键,所以修改不了元素的内容,而且如果元素很大的话,迭代的开销还是挺大的,所以要么你就变成for idx,_ := range m
这样的形式,用下标更新,要么就变成m := make([]*a,0)
这样的指针,这样虽然传的还是值,不过是个指针的值,一是开销小,二是可以直接修改元素内容了。
所以说,指针在go中还是不可或缺的一个存在,这也是为什么像我这种之前都是做C和C++的人喜欢go的原因,因为还是可以指针满天飞,写出只能自己看懂的代码出去装逼,然后告诉别人,还是有指针性能好啊。
如果你之前对指针没概念,或者一直没怎么理解指针,那go可能要用好还是要花点时间的,go确实入门很容易,但用好也不是那么容易,之前我开始用的时候,没仔细想过这方面的东西,而且特意减少了指针的使用,害怕出现C中的野指针的情况,后来越写越觉得不是那个味道,go把指针这个功能保留下来还是让你用起来的,后来写的代码就又开始偏C风格了,指针到处飞。
虽然如此,但为了安全性的考虑,go的指针还是有一些局限性的,各个类型之间的转换是不行的,像C语言那样把各种类型的变量通过指针转来转去是很难直接做到的,但是还是给有这种需求的人给开了个口子,那就是unsafe
包,看这个包的名字就知道是警告你,这是不安全的啊,挂了别来找我,我出这个包只是为了给你装逼用的。
比如我们有个需求,需要把一个结构体数组序列成一个byte数组后,还需要还原回来,一般的做法是序列化的方式,序列化成json或者用gob序列化成二进制,然后在反序列化回来,代码一般是这样的。
//do some append jsonbyte,err := json.Marshal(YYY) //do some thing structArray,err:=json.Unmarshal(jsonbyte,&XXX)
先不说序列化和反序列化都要耗费计算资源,影响速度,而且还有数据的拷贝,这对于一个高性能装逼语言写的高性能服务怎么能忍,那只能祭出指针神器了,并且还得用unsafe
包来加光环才行,一般情况下,序列化的过程中那次拷贝跑不掉,你总不需要需要序列化到本身吧,所以序列化的时候直接转成byte数组,当然,需要记录长度。
buffer := new(bytes.Buffer) err = binary.Write(buffer,binary.LittleEndian,YYY) lens=len(YYY) resBytes:=buffer.Buffer()
这时候,YYY结构体数组就序列化成了resBytes这个byte数组了,长度是lens,反序列化的时候,直接用指针和unsafe
包就行了,整个过程没有数据拷贝,也没有序列化和反序列开销,就像下面代码一样。
XXX := *(*[]structNode)(unsafe.Pointer(&reflect.SliceHeader{ Data: uintptr(unsafe.Pointer(&resBytes[0])),Len: int(lens),Cap: int(lens),}))
当然,这种适合的是structNode
结构体里面的元素是定长的,如果里面的元素还有byte数组或者string的话,甚至是int
这种和操作系统体系结构相关的元素,就真的是unsafe
了,呵呵。
上面两个例子,告诉我们,指针在golang中保留下来后,对性能有强需求的开发还是有好处的,并且,unsafe
包开放出来的功能,至少能让你知道数据在底层到底存到什么地址上,心里面也有底了。
坑爹的map
读写安全
对于map不是协程安全这一点,还是有些想吐槽的,其他语言很多也不是线程安全的,这本来没什么可说的,自己写代码的时候注意一下吧,但是golang本身就是以协程在语言中集成,开协程特别容易的语言,而且是鼓励大家多多使用协程的思维来编程,但是作为一个基础的集成到语言本身的数据结构,竟然不是协程安全的,我去,您至少提供一个协程安全的版本让大家去选啊,虽然加读写锁比较容易实现,但是也有几个问题:
有锁就必然需要考虑出现死锁的情况,而且问题还不好查。
由于加入了defer保留字,很多人在使用锁的时候基本上就是把开锁和关锁写在一起了,这样有时候代码改来改去,逻辑流程变了,容易导致死锁。
有人说可以把map自己封装成结构体嘛,开关锁就看不到了,但是很多时候,刚开始写的代码是不需要多协程的,这时候你用的map都是内部的map,当你发现需要锁这个map的时候,只有两种选择,一是把map封装成结构体,然后把所有的用了这个map的地方都改掉,二是在外面加一把锁,把会有冲突的地方锁起来,第一种方式改动有些大,第二种方式可能会产生bug。
要是有一个可以选的map实现方式就好了,要竞争的时候选读写安全的,不竞争的时候选简单粗暴的。
内存池的小坑
很多时候,我们会因为GC的问题,想自己做一个内存池,比较主流的做法就是用管道的方式来申请释放内存,现在也有sync.pool
包了。
但是用管道的方式来做内存池,只适合数组类型的数据,不适合map,因为数组的话,你只需要把len置为0,cap不变,吐出去就行了,这样会减少内存的申请开销,但是map的话,不删除key,这个key永远在,所以想用内存池来申请map是不行的。
当然,一般情况下也没有语言能支持map的内存池,只不过因为go的管道概念,让大家都觉得什么都可以往里面丢,做内存池的时候顺便就把map给支持了,这个坑就大了。呵呵,我就是。。。。。。
map和结构体
如果一个map的value是一个结构体的话,那你不能用map[key].sturct.ele=XX
给这个map中的这个结构体的元素赋值,还好是编译性的语言,会蹦一条编译cannot assign to
错误出来,算个小坑吧,由于map会在使用的过程中不断的申请新内存,拷贝对象到新内存中,所以直接的寻址是不支持的。
关于泛型
没有泛型是很多人觉得go语言不够人情味的一个地方,我也是其中之一,居然没有泛型,你叫人怎么写出装逼的,简洁的代码??!!而且golang的设计者们居然说不准备支持泛型(不过目前好像改口了,说Go2.0会考虑支持泛型,呵呵),这点简直了,为什么不支持泛型,难道interface{}就够用了?不停的类型判断必然导致代码的难看和性能的损失,这点都想不清楚吗?但是。。。。。
但是如果我们仔细想想泛型的实现就稍微理解了他们了,首先,泛型的实现有两种方式,一种是C++的模板方式,一种是JAVA的类型擦除(好像叫这个名字吧)方式,我们来看看这两种方式的泛型,再来猜猜看golang为什么不支持了。
-
C++方式的泛型实现是通过模板的,简单的说就是编译的时候通过分析这个泛型函数的调用方,然后产生出对应的函数,这样做的好处和坏处都很明显。
-
JAVA的泛型是通过类型擦除的方式来实现的,我本身不是写JAVA的,对这部分研究也不是很清楚,只知道他不是编译时替换类型的,而是把类型都擦除了,比如都变成obj了,在运行时需要的时候再转回来(对java这段描述不是很确定哈),个人觉得就是先把类型转成
*void
,这不就擦除了么,然后用的时候再转回来就行了哈。优势和劣势也很明显
好了,我们简单的说了一下泛型的原理,那么如果go要实现泛型的话,基本上就是这两种方式,第二种方式是不是感觉和interface有种似曾相识的赶脚呢?恩,看上去一样,还是有本质区别的,第二种java那种方式是JIT实现,而interface是runtime的运行时实现,效率差得不是一点半点的。如果用第一种方式进行编译时的模板扩展呢?同样会遇到代码增多的情况,golang的目标文件本来就是把所有东西都集成进行来了,本来就很大了,再这么整一下,估计目标文件更大了。
我觉得即便golang开放泛型,估计也是用第一种方式,因为如果用运行时的方式的话,给runtime调度器平增不少压力,而golang肯定不会用JIT吧,所以第二种实现方式估计有点够呛。
一些其他的
对于GC,就不吐槽了,因为毕竟,真有GC问题的话,我就用CGO了,呵呵,或者说在设计的时候就会直接考虑某些模块用C来做了,而且目前的go版本,GC已经很不错了,大部分应用没啥问题了,golang把协程集成进语言中,势必导致大家不计性能问题,奔放的开协程,那这个坑就只能google自己来填了,新版本(1.8)对GC的支持已经很好了,但是,对性能有强要求的服务,某些代码,还是用C吧,哈哈。
当然,要是go有一个不带gc的实现,自己来管理内存的版本,那就好了,语法比C舒服,还有协程和管道这些东西,要是能自己管理堆内存的话,那就完美了。
最后,还有一些没有说完的,这篇就不说了,下次接着聊聊channel和goroutine,以及和C的混合编程。
如果你觉得不错,欢迎转发给更多人看到,也欢迎关注我的公众号,主要聊聊搜索,推荐,广告技术,还有瞎扯。。文章会在这里首先发出来:)扫描或者搜索微信号XJJ267或者搜索西加加语言就行
原文链接:https://www.f2er.com/go/189009.html