年度语言 golang 使用感受
首先,无意进行语言之争,毕竟,PHP是世界上最好的语言,没有之一。这个话题可以停下来了。 2016年已经过去,16年的年度语言给了 这篇文章就不分什么优势和劣势了,想到哪里说到哪里。 指针还是很重要先看一个小坑,可能很多初次接触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("%vn",x.b) } } 在range中,后面那个元素是值传递,这个很关键,所以修改不了元素的内容,而且如果元素很大的话,迭代的开销还是挺大的,所以要么你就变成 所以说,指针在go中还是不可或缺的一个存在,这也是为什么像我这种之前都是做C和C++的人喜欢go的原因,因为还是可以指针满天飞,写出只能自己看懂的代码出去装逼,然后告诉别人,还是有指针性能好啊。 如果你之前对指针没概念,或者一直没怎么理解指针,那go可能要用好还是要花点时间的,go确实入门很容易,但用好也不是那么容易,之前我开始用的时候,没仔细想过这方面的东西,而且特意减少了指针的使用,害怕出现C中的野指针的情况,后来越写越觉得不是那个味道,go把指针这个功能保留下来还是让你用起来的,后来写的代码就又开始偏C风格了,指针到处飞。 虽然如此,但为了安全性的考虑,go的指针还是有一些局限性的,各个类型之间的转换是不行的,像C语言那样把各种类型的变量通过指针转来转去是很难直接做到的,但是还是给有这种需求的人给开了个口子,那就是 比如我们有个需求,需要把一个结构体数组序列成一个byte数组后,还需要还原回来,一般的做法是序列化的方式,序列化成json或者用gob序列化成二进制,然后在反序列化回来,代码一般是这样的。 //do some append jsonbyte,err := json.Marshal(YYY) //do some thing structArray,err:=json.Unmarshal(jsonbyte,&XXX) 先不说序列化和反序列化都要耗费计算资源,影响速度,而且还有数据的拷贝,这对于一个高性能装逼语言写的高性能服务怎么能忍,那只能祭出指针神器了,并且还得用 buffer := new(bytes.Buffer) err = binary.Write(buffer,binary.LittleEndian,YYY) lens=len(YYY) resBytes:=buffer.Buffer() 这时候,YYY结构体数组就序列化成了resBytes这个byte数组了,长度是lens,反序列化的时候,直接用指针和 XXX := *(*[]structNode)(unsafe.Pointer(&reflect.SliceHeader{ Data: uintptr(unsafe.Pointer(&resBytes[0])),Len: int(lens),Cap: int(lens),})) 当然,这种适合的是 上面两个例子,告诉我们,指针在golang中保留下来后,对性能有强需求的开发还是有好处的,并且, 坑爹的map读写安全对于map不是协程安全这一点,还是有些想吐槽的,其他语言很多也不是线程安全的,这本来没什么可说的,自己写代码的时候注意一下吧,但是golang本身就是以协程在语言中集成,开协程特别容易的语言,而且是鼓励大家多多使用协程的思维来编程,但是作为一个基础的集成到语言本身的数据结构,竟然不是协程安全的,我去,您至少提供一个协程安全的版本让大家去选啊,虽然加读写锁比较容易实现,但是也有几个问题:
要是有一个可以选的map实现方式就好了,要竞争的时候选读写安全的,不竞争的时候选简单粗暴的。 内存池的小坑很多时候,我们会因为GC的问题,想自己做一个内存池,比较主流的做法就是用管道的方式来申请释放内存,现在也有 但是用管道的方式来做内存池,只适合数组类型的数据,不适合map,因为数组的话,你只需要把len置为0,cap不变,吐出去就行了,这样会减少内存的申请开销,但是map的话,不删除key,这个key永远在,所以想用内存池来申请map是不行的。 当然,一般情况下也没有语言能支持map的内存池,只不过因为go的管道概念,让大家都觉得什么都可以往里面丢,做内存池的时候顺便就把map给支持了,这个坑就大了。呵呵,我就是。。。。。。 map和结构体如果一个map的value是一个结构体的话,那你不能用 关于泛型没有泛型是很多人觉得go语言不够人情味的一个地方,我也是其中之一,居然没有泛型,你叫人怎么写出装逼的,简洁的代码??!!而且golang的设计者们居然说不准备支持泛型(不过目前好像改口了,说Go2.0会考虑支持泛型,呵呵),这点简直了,为什么不支持泛型,难道interface{}就够用了?不停的类型判断必然导致代码的难看和性能的损失,这点都想不清楚吗?但是。。。。。 但是如果我们仔细想想泛型的实现就稍微理解了他们了,首先,泛型的实现有两种方式,一种是C++的模板方式,一种是JAVA的类型擦除(好像叫这个名字吧)方式,我们来看看这两种方式的泛型,再来猜猜看golang为什么不支持了。
好了,我们简单的说了一下泛型的原理,那么如果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或者搜索西加加语言就行
(编辑:李大同) 【声明】本站内容均来自网络,其相关言论仅代表作者个人观点,不代表本站立场。若无意侵犯到您的权利,请及时与联系站长删除相关内容! |