年度语言 golang 使用感觉

首先,无心进行语言之争,毕竟,PHP是世界上最好的语言,没有之一。这个话题能够停下来了。javascript

2016年已通过去,16年的年度语言给了go语言,而正好这一年我都是用go用得比较多,并且版本从1.2一直用到了1.8,有一些感觉,来讲说我对这个年度编程语言的一些粗浅理解吧。以前也写过一篇go语言的文章,可是那时候用得还不是不少,有些特性没有用上,因此理解上和今天的有些不一样。java

这篇文章就不分什么优点和劣势了,想到哪里说到哪里。golang

指针仍是很重要

先看一个小坑,可能不少初次接触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)这样的指针,这样虽然传的仍是值,不过是个指针的值,一是开销小,二是能够直接修改元素内容了。json

因此说,指针在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数组,固然,须要记录长度。app

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,这不就擦除了么,而后用的时候再转回来就好了哈。优点和劣势也很明显
    • 好处就是代码不会膨胀了。
    • 劣势就是这样的话,运行时仍是须要作类型的判断,增长了消耗,可能还会不安全,由于只要是运行时判断,你就有可能对一个int类型插入一个string。

好了,咱们简单的说了一下泛型的原理,那么若是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或者搜索西加加语言就行

相关文章
相关标签/搜索