• Ready? Go!


    转自:http://monnand.me/p/ready-go-2/zhCN/

    Go语言是Google于2009年推出的静态编译型语言,旨在为开发人员提供类似Python,Ruby一样简洁的语言环境,同时又具备C/C++一样的运行效率。作为一个开源项目,在过去的两年多时间里,Go以社区协作的形式,不断地完善语言和标准库的设计与实现。最终于今年三月28日发布了第一个稳定的发行版本:Go version 1,简称Go 1。Go 1的推出,意味着Go语言和它的标准库已经进入了一个稳定阶段。对于谨慎的开发人员来说,开发Go程序正趁当下,现在已经可以放心地开发Go程序,不必再考虑未来语法和标准库的变化。

    正如Go官方所说,Go 1的目的是发行一个稳定的Go语言实现,而非全盘修改。所以对于已经熟悉Go的开发人员来说,Go 1与之前的版本并没有很大差异。绝大部分修改都是对标准库命名空间的再组织。本系列文章包含上下两篇,上篇重点讨论Go的开发背景,部分语法和类型系统;下篇讨论Go的并发模型和工具链。

    本文的全部代码可以在github上找到。

    编译效率,运行效率,开发效率

    曾经那些代码,品读起来,恰似满腹经论的学者之间,细语轻声,拂琴畅谈。绝非乌烟瘴气之下,面红耳赤地与编译器争辩。--- Dick P. Gabriel

    按照Go官方FAQ的说法,Go的出现是为了弥补其他语言在系统级开发上的缺陷。这样一句话,难免有人会觉得,Go的诞生纯粹是几位计算机界大佬 --- Go最初的核心开发人员包括Robert GriesemerRob PikeKen Thompson --- 在埋怨自己的不肖后辈,并在吐槽同时,自己亲自操刀开发了这样一个语言。但更中肯地说法,恐怕是他们在目睹和体验了Google的系统级开发之后,总结出的一套在异构,分布式多核系统之上的生存之道。而今天Google所面临的问题,也许恰恰是几年后每个公司的面试题目。与其说Go是一座空中楼阁,不如说是各位系统开发界大佬进入新时代后的一部略带辛酸的开发史。

    Go的基本设计理念是:编译效率,运行效率和开发效率要三者兼顾。使用Go开发,要让开发人员感觉到Python的便利,C/C++的运行效率,以及小到可以被忽略的编译时间。为了实现这个理念,形成了Go语言的以下几个特性:

    • 编译,静态类型语言。由此可以提供满足对运行效率敏感的系统级应用。
    • 垃圾回收,去除复杂的内存释放工作。
    • 简洁的符号和语法,极力减少开发人员输入的字符数。
    • 平坦的类型系统,去除了复杂的继承关系。使用结构化类型系统(Structural type system),既简化了事前设计工作,也为未来增加抽象层提供了非侵入式的解决方法。
    • 基于CSP模型的并行,简化了并发结构之间的通信和数据共享。为多核时代的程序开发打好基础。
    • 比线程更轻量的goroutine,让一个线程可以执行多个并发结构。不必使用异步通信,就足以达到线程池与select/poll/epoll的效果。极大简化了多连接的开发。
    • 使用一套简单的规范,开发人员不必再单独编写脚本指定依赖关系和编译流程。仅仅使用代码本身和go工具链,就可以处理各种依赖关系。写完代码,一条命令,自动下来各种依赖,直接编译/安装。无需make,autoconf,automake,setup.py等工具支持。

    前两点应该是不言自明的。本系列文章重点对后五点做详细分析。本篇后半部分将讨论语法和类型系统;下篇将讨论并发模型和工具链。关于如何提高编译效率,由于涉及较多编译器实现细节,在此不讨论。

    化繁为简,语法当先

    public static <I, O> ListenableFuture<O> chain (ListenableFuture<I>input, Function<? super I, ? extends ListenableFuture<? extends O>>function) 苍天啊!大地啊!快把这货拦下来吧!--- 来自某聊天记录

    初学Go,会让人感到它神似C语言,并非是其背后强大的开发团队和他们与C语言千丝万缕的联系,也不仅仅是Go对系统级开发的重视和它类C的语法。而是简洁与实用并存的语法让人触目难忘。在21世纪,一个严肃的通用编程语言,使用一份仅有约两万词的语言规范,只定义25个关键字,42个操作符,却涵盖了并发,面向对象等方方面面,仅仅这些,对于开发人员来说,这个语言怕也足以值得一试了。

    这里列举部分语法:

    • 没有分号了。其实Go语言规范中,Go中的语句的确是以分号做分割的。但Go语言规定,词法分析器使用一套规则自动地添加分号。这种对词法分析器的要求,带来了一条编码规范:左大括号不要单独写在一行,否则词法分析器可能会产生不必要的分号。虽然多了这样一条编码规范,但是带来的结果是开发人员确实不必考虑分号了。以Go标准库为例,其中没有任何一个语句,需要开发人员明确地写下分号。
    • 使用:=操作符声明变量和其初始值,不必明确指明变量类型,因为初始值已经说明了变量的类型。试想要声明一个结构/类。使用Java需要foo.Foo a = new foo.Foo;, 而Go则只需a := new(foo.Foo)。对于比较长的类型名来说,这可以减少很多打字。需要注意的一点是,:=操作符同时完成了变量声明和赋值的操作。如果变量之前已经声明,只是要给它赋值,则依然使用常见的=赋值。
    • for,if的判断条件,和switch的控制语句无需括号。也就是说,可以写成if a < b { dosomething() }。还可以写switch b { case 1: dosomething() }
    • 所有循环只有for一个关键字。而for循环有可以有几种写法: 和C语言for循环类似的写法for i := 0; i < 10; i++ { dosomething() };和C语言while循环类似的写法for i < 100 { dosomething() };以及无条件循环for { iteration() };另外,对于切片类型(类似于Java中的数组,是一种线性结构)和map类型(go中的哈希表实现),还可以配合range关键字,遍历存储的成员,如for i, e := range list { dosomething(i, e) }
    • 直观简单的访问控制。只有名字以大写字母开头的变量,函数/方法,或结构,才能被包(package)外代码访问。否则只是包内可见。这不仅简化了访问控制,而且对开发者来说,只需要看到名字,不必去找声明,就可以知道访问条件。
    • switch的条件可以是表达式且可以没有控制语句。这意味着以下语句是合法的:
    switch {
        case i % 2 == 0:
            process_even(i)
        case i % 2 != 0:
            process_odd(i)
        }

    这些看似不经意的改变,却有效的简化了程序的书写和阅读。让开发者减少打字之外,也让读程序变得更加简单。由于这些改变和特性并不复杂,在此不多加介绍。

    正交原则:数据,方法,复用和抽象

    早知[C++]如此[复杂],要是能穿越回去,我们肯定会搞一个面向对象版本的C语言出来。--- Ken Thompson,UNIX创始人,Go语言作者,在接受采访时关于Go和面向对象。

    这一节主要讨论Go的类型系统。和C语言一样,Go也使用结构体进行数据抽象:

    type Duck struct {
        Name string
    }

    接下来,可以为这个结构定义一个方法:

    func (d *Duck) Eat() {
        fmt.Println(d.Name, 
            "Duck is having dinner!")
    }

    与C++,Java的类不同,为结构体添加方法不必在声明结构体的时候就声明该方法。只需要保证方法与结构体定义在同一个包(package)中。由此数据与行为被分离,在设计数据抽象(结构体)阶段,不必考虑具体哪些行为。func关键字后面的(d *Duck)表示这个方法从属于哪个类型。d这个变量被成为“接收者”(receiver),在方法的定义中,d的使用类似C++/Java中的this指针。

    然后,我们就可以使用这个结构体和它的方法了:

    func main() {
        d := new(Duck)
        d.Name = "Donald"
        d.Eat()
    }

    按照讲述面向对象语言的规律,下一步应该介绍继承机制了。Go的回答很简单:没有继承。这听起来似乎不可理喻,但却带来了直接的好处:极大地简化了类型系统。一方面,编译器可以更高效了;另一方面,开发人员不必事前考虑各种复杂的继承关系。

    但是,继承的好处也是非常明显的:第一,复用,子类可以直接继承父类的方法的实现,减少了重复代码;第二,多态,开发人员可以使用更抽象的表示(父类)调用具体的实现(子类),由此可以编写通用的代码在抽象层次上进行操作。

    Go如果只是粗鲁地去掉了继承机制,而不去面对继承所要解决的问题,显然是不明智的。为此,Go分别使用两套机制来实现继承要达到的效果:匿名成员来实现代码复用;接口类型实现类型抽象。

    实现代码复用,Go在结构体定义中,引入了“匿名成员”(Anonymous Field)的概念。了解面向对象设计的开发者一定听说过,要优先使用对象组合而非继承的方式来实现代码复用的原则。Go的匿名成员,实际也就是一种对象组合。

    继续上面的例子,假如需要定义一个DonaldDuck类型,它的Eat方法实现和Duck一样,但是多了一个叫做Age的成员:

    type DonaldDuck struct {
        Duck
        Age int
    }

    可以看到,仅仅是把Duck这个类型名字放在结构体的定义中。由于并没有显示地给出这个成员的名字,由此得名“匿名成员”。这个写法本质上是定义了另外的一个结构体,它包含了一个类型为Duck,名字也叫做Duck成员(是的,这个成员名和类型名是一样的)。同时,也包含了一个类型为整数,名为Age的成员。

    那么这匿名成员究竟对代码复用有什么意义呢?Go对匿名成员有一条特殊规则:包含匿名成员的结构体也具有了匿名成员类型的方法。简单说,对于上面的例子,DonaldDuck中包含了匿名成员Duck,那么就好像DonaldDuck实现了Duck的各种方法。这样,下面的代码就容易理解了:

    func main() {
        d := new(DonaldDuck)
        d.Name = "Donald"
        d.Age = 10
        d.Eat()
    }

    我们首先需要申请一个DonaldDuck类型的对象,然后对这个对象的各个成员进行赋值,最后调用Eat()方法。至今为止,我们看到,使用匿名成员可以实现像继承一样的代码复用。但是比起继承,它又少了点东西。比如,你无法将一个DonaldDuck类型的对象地址赋值给一个Duck类型的指针。你也不能在DonaldDuck中重新定义Eat方法的实现。简单说来,匿名成员仅仅在语法层面上做了一些简化,并没有触及任何类型系统的内容。对于类型系统来说,DonaldDuck和Duck完全是两个不同类型。

    为了实现继承机制的另外一部分,即多态,Go引入了接口类型的概念。与Java中的接口类似,Go的接口也是声明了一组方法的原型,然后由具体结构体(类)的方法来实现各种接口。但是与Java不同的是,Go使用了类似OCaml的结构化类型系统(Structural Type System),这种类型系统不要求实现接口的类型显示地声明究竟要实现哪些接口。只需要定义好与接口类型一致的全部方法,就说该类型实现了这个接口。具体说来,对于以上代码,我们可以定义一个Animal的接口:

    type Animal interface {
        Eat()
    }

    这个接口中仅仅定义了一个方法:Eat,它没有任何输入参数,也没有返回值。至此为止,Duck和DonaldDuck都是Animal这个接口的实现。没错,你不必修改Duck和DonaldDuck的代码,不必显示的写明implements Animal,只要实现了Eat方法,并且原型与接口类型中定义的一致就可以了。那么,我们就可以直接声明一个Duck类型的指针,然后把它赋值给Animal接口类型的变量:

    func main() {
        var a Animal
        d := new(Duck)
        d.Name = "Don"
        a = d 
        a.Eat()
    }

    如果还要哪些类型实现Animal这个接口,只需要为这些类型实现Eat方法,就可以了。

    这种接口类型为开发人员提供了方便的“先实现,后抽象”的机制。因为接口本身是非侵入式的,不必在定义结构的时候就明确说明要实现哪些接口。开发人员可以在实现了一些结构之后,再寻找它们之间通用的接口表示形式,进而定义一个接口类型。这时候,任何实现了接口中指定方法的类型,都已经是这个接口的实现了。不必再修改以前运行良好的代码,也极大地简化了开发流程。试想,在几万行代码中,寻找几个实现了某些方法的类型,哪怕只在每个类型的定义中添加两个单词,也足以让人生厌了。

    这种“先实现,后抽象”的机制也符合我们认识世界的一般规律。哪怕是自己写的代码,往往也是在使用一段时间后,才发现它们背后的一些抽象表示。单纯地要求在代码开发之前的设计阶段就定义好良好的类型层次,是一种无视人类认识局限的荒谬做法。Go作为一种实用的语言,承认这种局限的同时,为人们提供了最小修改代价的方案。

    Go的类型系统体现了Go的另一个设计原则:正交原则。线性代数中,正交意味着一组向量之间没有任何一个可以投影到其他向量上。引申到程序设计领域,就是任何特性只针对一个问题,并且互相之间没有交集。具体到Go的类型系统,则体现在方法与数据的分离;和复用与抽象的分离。这样的分离使得开发者可以通过分析问题本身,有效地针对问题选择不同特性。

    小结

    本篇重点讨论了Go语言的语法和类型系统。强调了Go的一些基本设计原则。这一切都是以简洁为基础,试图以最小的语言特性覆盖各种常见问题,这酷似当年的C语言。尽管这样的设计也许不会迎来多少学术界的赞誉,却足以把开发者从复杂的语法设计,层层的类型关系中拯救出来,用更清晰简单的方法解决手头的问题。

    但只有这些还不足以令Go续写C语言的辉煌。它必须要正视它所处时代必须面对的问题,这就是多核带来的挑战。如今的程序,不能再指望仅仅提高在单核上的运行效率,而提高整个程序的效率。面对多核时代,开发者必须要发掘程序中潜在的并行结构,最大化利用多核的并行能力。而Go则为开发者提供好了称手工具,去迎接多核带来的新挑战。下期我们将重点讨论Go语言中对于并行结构的处理,与go工具链。敬请关注。

    并行和goroutine

    然而,处理器技术的发展指出,比起[掩盖了各种并行结构的]单处理器,由多个类似的处理器(各自包含自己的存储单元)组成的多处理器计算机也许会更加强大,可靠和经济。 --- C.A.R. Hoare,图灵奖获得者,CSP作者,于1978年

    20世纪六七十年代,为了弥补处理器的处理能力,并行计算曾一度成为研究热点。期间不乏优秀的想法,如信号量(Semaphore),管程(Monitor),锁(mutex)以及基于消息传递的同步机制。但八十年代起,随着单核处理器性能飞速提高,学术界迎来了并行计算的黑暗时期。六七十年代的研究成果中,只有早期的一些思想被大规模使用在实际开发中。而七十年代后期的很多成果甚至还没被大规模应用,就伴随着并行计算黑暗期的到来,或不温不火,或被收藏入库。CSP(Communicating Sequential Processes)便是其中之一。但它优雅简洁的处理方式却依然在一些小众语言中流传了下来。如今,由于能耗和散热问题,处理器的发展转而以多核的方式提高处理器性能。我们再次迎来了曾经面对过的并行计算。这时候,CSP模型逐渐展露头脚。

    CSP的基本思路是基于消息机制的同步和数据共享。与传统的锁同步不同,消息机制简化了程序设计,并且可以有效地减少潜在bug。基于CSP模型的语言主要有三个分支:忠于原始CSP设计,以Occam为代表的一支;强调网络和模式,以Erlang为代表的一支;再一个就是强调传递消息的信道(channel),以SqueakNewsqueakAlefLimboGo为代表的一支。值得一提的是,第三支的语言中,大部分都是有Rob Pike主持或参与开发的,其中自然也包括Go。

    既然说起Go的这一分支是以强调信道(channel)为特色,那么就先从Go的信道说起。Go的信道是一种数据类型,goroutine可以使用它来传递数据。至于goroutine是什么,之后会详细讨论。此处仅需把它理解为与线程类似的运行时结构即可。

    定义一个信道,需要指定这个信道上传递的数据类型。可以是int,float32,float64等基本数据类型,也可以是用户自定义的结构体,接口,甚至可以是信道本身。

    ch := make(chan int)

    这样,就定义了一个传递整数类型的信道。如果要从这个信道中读取一个值,则可以使用<-操作。类似的,写入则使用->操作符:

    // 从ch中读取一个值存入i中
    i := <- ch
    
    // 向ch中写入j的值
    ch <- j

    信道的操作是同步的,一个读操作只有在真正读到内容之后,才继续执行下面的语句;而写操作则只有在写入数据被信道另一端读到,才执行之后的语句。(Go中信道也可以加入缓存队列,在此不多讨论)

    同时,对于信道,还允许使用for循环依次处理来自信道的内容:

    func handle(queue chan *Request) {
        for r := range queue {
            process(r)
        }
    }

    这个函数的任务就是不断地从信道中读取Request结构体的指针,然后调用process函数进行处理。

    除此以外,还可以使用select对多个信道进行读写操作:

    func Serve(queue chan *Request,
               quit chan bool) {
        for {
            select {
            case req := <- queue:
                process(r)
            case <- quit:
                return
            }
        }
    }

    这个函数接受两个信道作为参数。第一个信道queue用来传递各种请求。第二个信道quit则用来发布一条信令,告诉该函数返回。

    接下来要说的,就是goroutine。它是一种比线程还要轻量的并行结构。在Go程序运行时,一般会并行运行几个线程,然后把goroutine分配到各个线程中。当一个goroutine结束或者被阻塞的时候,另外一个goroutine将被调度到被阻塞或结束的goroutine所在的线程中。这样的调度保证了每个线程可以有较高的使用率,不必一直处于阻塞状态。由此省去了很多操作系统调度线程而导致上下文切换。按照Go官方的说法,一个Go程序同时运行几万到几十万个goroutine是非常正常的。

    使用一个goroutine也非常简单,只要在函数调用前面加入go就可以了:

    go process(r)

    这样,process这个函数就单独运行在一个goroutine中了。

    由此带来的结果,就是极度地简化了服务器端对并发连接的处理。众所周知,如果让一个线程只处理一个用户连接,那么开发起来会非常简单,但是效率不高;而如果一个线程处理多个用户连接,又无端增加了开发难度。而配合信道使用goroutine则在不增加开发难度的同时,也提高了效率。

    考虑这样一个应用场景:服务器从网络接收客户端请求,做一些处理,再把结果返回给客户。

    对于不同的用户连接,用不同的goroutine处理。定义名为UserConn的结构体来表示一个用户连接。同时,这个结构体定义了一个叫做ReadRequest的方法,用于从网络读取用户的请求;还有一个叫做WriteResponse的方法,用于从网络给用户传递结果。作为一个想象的例子,具体的实现细节在此不详述。

    那么,对于每个连接,要做的事情大约如此:

    func ServeClient(conn *UserConn) {
        ch := make(chan *Response)
    
        // 创建一个goroutine,
        // 专门用于向用户发送结果
        go writeRes(conn, ch)
    
        for {
            // 读取一个请求,
            //  判断类型
            // 如果用户请求关闭,
            //  则函数返回
            req := conn.ReadRequest()
            switch req.Type {
            case NORMAL_REQUEST:
                go process(req, ch)
            case EXIT:
                return
            }
        }
    }

    writeRes和process的基本结构大约如下:

    func writeRes(conn *UserConn,
                 ch chan *Response) {
        for r := range ch {
            conn.WriteResponse(r)
        }
    }
    
    func process(req *Request,
                ch chan *Response) {
        res := calculate(req)
        ch <-res
    }

    信道本身很符合人们对于通信工具的直觉定义,开发者可以很自然地使用信道在goroutine之间建立各种关系。使用信道和goroutine,每个函数要完成的任务都被单一化,减少了发生错误的可能。代码中,通过传递指针的方式来共享内存空间,在每次共享之前,都是以消息进行同步。这又是一条Go的原则:用传递消息来共享内存;而不是用共享内存来传递消息。由此简化了并行程序的开发。

    作为一个实用的编程语言,Go并没有按照CSP原始论文中说的,仅仅提供信道的方式来进行同步。Go在标准库中也提供了基于锁,信号量等传统同步机制的工具。在以上代码中,其实存在着一个潜在bug:ServeClient函数不是在所有运行process的goroutine执行结束后再退出,而是在一收到来自客户端的退出命令后直接退出的。更合理的操作应该在所有处理该连接的goroutine都退出后再返回。在标准库中,有一个WaitGroup结构体就可以专门解决等待多个goroutine的问题。在此不详述。

    接下来,就是为每个用户连接开启一个goroutine,执行ServeClient函数。前面已经说过,由于goroutine是一种比线程还轻量的调度单位,如此数目的goroutine并不会带来严重的性能下降。

    由于goroutine和消息机制简化了开发,并且Go也鼓励这样的设计,开发者会自觉地选择基于多个goroutine的设计。由此带来的另一个好处,就是程序在多核系统上的扩展性。随着处理器核数量的增加,如何发掘程序内在的并行结构成了当前开发人员面临的很大挑战。而使用Go编写,基于多个goroutine的设计,往往会天生具备着足够的并行结构来扩展到多核处理器之上。每个goroutine实际都是可以放在一个独立的处理器上,与其他goroutine并行执行。也就是说,今天为四核处理器写的代码,也许不必修改,就可以运行在未来128核的CPU上,并且同时使用所有的核。

    无需配置,直接编译

    如果Go需要一个配置文件,描述如何编译和构建Go写的程序,那就是Go的失败。 --- Go官方文档

    对于make,autoconf,automake等用于指定编译顺序和依赖关系的工具,Go的态度是:开发者在写代码的时候,就留下了关于依赖的足够信息,不该要求开发者再单独写一份配置文件,去指明依赖关系和编译顺序。为此,开发者只需要在安装go工具链之后,按照官方文档,配置好一个目录结构和一个环境变量即可。以后任何安装Go程序,编译任何Go程序/库都只需要几条简单的命令就可以了。

    对于一个自包含(不依赖任何第三方库)的程序,只需要在当前目录下运行go build就会编译好整个程序。

    如果我的程序依赖第三方库,又该如何呢?很简单,在代码中的import语句里,写入第三方库的在网络中的位置即可。这里的import和Java/Python中的import的概念一样,都是引入一个包。

    import (
        "fmt"
        "github.com/monnand/goredis"
    )

    import中引入的第一个包,是fmt,这是标准库中的包,提供Printf一类的格式化输入和输出。第二个引入的包则是位于github上的代码库。它会引入github上,用户monnand下,goredis这个项目定义的包。

    接下来,再调用go命令安装这个库:

    go get github.com/monnand/goredis

    这样,go程序就会自动下载,编译和安装这个库(包括它的依赖)。接下来再使用go build编译依赖goredis的程序。

    除此以外,如果依赖goredis的程序也在github(或其他go支持的版本控制库)中,那么只用一条go get命令指明该程序所在的远程地址就足够了,go会自己下载安装各种依赖。除了github,go还支持google code,BitBucket,Launchpad,或者是任何位于其他服务器上,使用svn,git,Bazzar,Mercurial做版本控制的Go程序/库。这一切都极大地简化了开发人员和最终用户的操作。

    再谈运行效率

    • Matt: 使用Pat/Go后,比起(原来的)Sinatra/Ruby方案,JSON API节点效率提升了多少?给个估计就可以。
    • Blake: 大约10,000倍
    • Matt: 漂亮!我能引述你的话吗?
    • Blake: 我再查查,我觉得好像低估了。

    --- Matt Aimonetti与Blake Mizerany在推特上的对话。

    Go程序的运行效率一直是人们关注的焦点。一方面,Go的语法,类型系统都非常简单,为编译器的开发和优化提供了很大空间。另一方面,Go作为静态编译型语言,代码直接编译为机器码,无需中间解释。

    不过倘若在网上搜索一下,就会发现关于Go程序的运行效率,存在着严重的两极分化。一部分测试显示,Go的程序运行效率非常高,甚至一些方面超过了C++写的同等程序。另一部分测试则现实,某些方面,Go甚至不如Stackless Python写的脚本。

    Go编译器本身虽然还存在很大优化空间,但产生的机器码效率已经比较高。而标准库 -- 其中包括各种运行时代码,比如垃圾回收,哈希表等 -- 则还没有怎么优化,甚至有些还处于很初级的阶段。这是网络上的测试结果存在着严重差异的原因之一。另外,作为一个新的语言,开发人员由于对它不熟悉,写出的代码可能存在性能瓶颈,也加大了评测结果的差异。

    Go语言的开发者之一,Russ Cox曾在Go的官方博客上发表了一篇文章。其中使用了某基准测试程序(Benchmark)的代码,分别优化了其中的C++测试和Go测试部分。优化后的Go程序运行时间,甚至仅仅是优化后的C++程序运行时间的65.8%!这也从一个侧面反应出了Go的潜力。

    当前Go语言中,还存在不少缺陷:垃圾回收还处于比较初级的阶段,而且对于32位系统的支持还不太完善,一些标准库的代码还有待优化。按照Go官方的说法,未来将会使用完全并行的垃圾回收器,这对于性能来说将会有很大的提高。而随着Go 1的发布,Go开发组也会将精力从语法和标准库的规范,转移到对编译器和标准库的优化上。Go程序的运行效率,目标将会是逼近C++,超越Java。

    总结

    现在来说,我觉得在系统级开发方面,它(Go)比C++要好上许多。使用它开发更高效,并能使用比C++更简单的方式解决很多问题。---- Bruce Eckel, 《C++编程思想》《Java编程思想》作者

    Unix创始人Ken Thonpson;UNIX/Plan 9开发者Rob Pike,Russ Cox;memcached作者Brad Fitzpatrick;Java Hotspot编译器作者之一,Chrome V8引擎作者之一Robert Griesemer;Gold连接器作者,GCC社区活跃开发人员Ian LanceTaylor……当这样一群人凑在一起,无论开发什么,这团队本身也许已经足以吸引众人眼球了。而Go作为这样一个团队开发出的语言,目前为止还是给不少人带来了惊喜。

    已经有很多公司使用Go开发生产级程序。Rob Pike曾透露过Google内部正逐渐开始使用Go。YouTube则使用Go编写核心部件,并且将部分代码组织成了开源项目vitess。国内包括豆瓣,QBox等公司也已经率先踏入Go语言这个领域。

    随着Go 1的推出,一个稳定的Go语言平台和开源社区已经形成。对于喜欢尝试新鲜语言的开发者,Go不失为一个选择。

  • 相关阅读:
    集群
    Zabbix分布式监控系统
    构建读写分离的数据库集群
    MySQL主从配置
    常用MySQL操作
    Prometheus Node_exporter 之 Network Netstat ICMP
    Prometheus Node_exporter 之 Network Netstat UDP
    Prometheus Node_exporter 之 Network Netstat TCP Linux MIPs
    Prometheus Node_exporter 之 Network Netstat TCP
    Prometheus Node_exporter 之 Network Netstat
  • 原文地址:https://www.cnblogs.com/sevenyuan/p/3056262.html
Copyright © 2020-2023  润新知