Go程序编译简介

Go语言安装包自带的编译器是gc。针对不同的硬件平台,Go分别提供了一组编译工具包:比如,6a6c6g6l就是针对X86-64平台(5*ARM8*X86)。此外gcc还有一个支持Go语言的前端:gccgo。因此如果gc不支持你当前的平台,可以尝试用gcc

go build命令默认会使用gc编译器。假设当前目录为goEx,在这个目录下创建hello.go

package main

import "fmt"

func main() {
        fmt.Println("Hello, world!")
}

在当前目录下执行go build命令:

[root@Fedora goEx]# ls
hello.go
[root@Fedora goEx]# go build
[root@Fedora goEx]# ls
goEx  hello.go
[root@Fedora goEx]# ./goEx
Hello, world!

可以看到生成的可执行文件名字为goEx,与目录名相同。

因为hello.gomain package,执行go build hello.go会生成名为hello的可执行文件:

[root@Fedora goEx]# go build hello.go
[root@Fedora goEx]# ls
hello  hello.go

也可以使用-o选项指定可执行文件名字:

[root@Fedora goEx]# go build -o a.out hello.go
[root@Fedora goEx]# ls
a.out  hello.go
[root@Fedora goEx]# ./a.out
Hello, world!

Go语言的new函数

Go语言有一个内置的new函数,其定义如下:

func new

    func new(Type) *Type  

The new built-in function allocates memory. The first argument is a type, not a value, and the value returned is a pointer to a newly allocated zero value of that type.

其输入参数是一个类型,返回是一个指向该类型内存的指针,且指针所指向的这块内存已被初始化为该类型的0值。下面例子演示了如何使用new函数:

package main

import (
    "fmt"
)



func main() {
    v := new(int)
    *v++
    fmt.Println(*v)
}

运行结果如下:

1

new函数的出现早于make&{},并且它可以让人直观地认识到返回值就是一个指针,在某些情形下,这会让人更清晰地理解代码。

参考资料:
(1)http://golang.org/pkg/builtin/#new
(2)Why is there a “new” in Go?

Goroutine浅析

GoroutineGo语言并发编程中的一个重要概念,事实上我们可以把它理解成为非常轻量级的线程(thread)或协程(coroutine)。每个运行的 Go程序都至少包含一个goroutine——运行main函数的goroutine

创建goroutine要使用go语句:

go function(arguments)
go func(parameters) { block } (arguments)

go后面要跟一个已经定义的函数或是一个现定义的匿名函数。执行完这条语句后,就会启动一个独立的,和其它程序中已有的goroutine并行运行的新的goroutine

runtime程序包中提供了一个GOMAXPROCS函数,它可以用来设定实际上有多少个操作系统线程来执行Go程序。截止到目前为止(1.4.2版本),这个值一直默认为1。从1.5版本起,Go的开发者打算把这个默认值改为可以使用的CPU个数,并认为这会改善Go程序的性能。看一个简单的程序:

package main

import (
        "fmt"
        "time"
        "runtime"
)

func sleep(s string) {
        for {
                fmt.Println(s)
                time.Sleep(time.Second)
        }
}

func main() {
        fmt.Println(runtime.GOMAXPROCS(0))
        go sleep("Hello")
        go sleep("World")
        time.Sleep(1000 * time.Second)
}

main函数第一行打印了当前系统默认设置的GOMAXPROCS的值。另外在main goroutine里启动了两个goroutine,分别交替地打印“Hello”和“World”。执行如下:

1
Hello
World
Hello
World
Hello
World
Hello
World
......

可以看到默认的GOMAXPROCS的值的确为1。“Hello”和“World”也被交替打印了。在执行过程中,使用pstack命令查看进程的线程调用栈:

[root@Fedora ~]# pstack 10773
Thread 1 (LWP 10773):
#0  runtime.futex () at /usr/local/go/src/runtime/sys_linux_amd64.s:278
#1  0x000000000042b197 in runtime.futexsleep () at /usr/local/go/src/runtime/os_linux.c:49
#2  0x000000000040adae in runtime.notesleep (n=0x54df18 <runtime.m0+216>) at /usr/local/go/src/runtime/lock_futex.go:145
#3  0x000000000042e669 in stopm () at /usr/local/go/src/runtime/proc.c:1178
#4  0x000000000042f4c2 in findrunnable () at /usr/local/go/src/runtime/proc.c:1487
#5  0x000000000042f881 in schedule () at /usr/local/go/src/runtime/proc.c:1575
#6  0x000000000042fb03 in runtime.park_m () at /usr/local/go/src/runtime/proc.c:1654
#7  0x000000000041f94a in runtime.mcall () at /usr/local/go/src/runtime/asm_amd64.s:186
#8  0x000000000054dab0 in runtime.g0 ()
#9  0x000000000042de04 in runtime.mstart () at /usr/local/go/src/runtime/proc.c:836
#10 0x000000000041f84c in runtime.rt0_go () at /usr/local/go/src/runtime/asm_amd64.s:106
#11 0x0000000000000001 in ?? ()
#12 0x00007ffd43803348 in ?? ()
#13 0x0000000000000001 in ?? ()
#14 0x00007ffd43803348 in ?? ()
#15 0x0000000000000000 in ?? ()

可以看到的确只有一个线程在运行。

Goroutine的调度器(scheduler)并不是完全抢占的(fully-preemptive),当有GOMAXPROCSgoroutine并行运行时,运行的goroutine必须要放弃执行权,其它goroutine才有机会得到运行(比方说,调用time.Sleep())。此外,取决于调度器的实现,goroutine可能在执行任何函数时,都会放弃CPU。看下面这个程序:

package main

import (
    "fmt"
)

func say(s string) {
    for i := 0; i < 5; i++ {
        fmt.Println(s)
    }
}

func main() {
    go say("world")
    say("hello")
}

执行结果如下:

hello
hello
hello
hello
hello

main goroutine运行起来以后,并没有给另外一个goroutine打印“world”的机会。随着main goroutine执行完毕,这个程序也退出了。修改程序如下:

package main

import (
    "fmt"
    "time"
)

func say(s string) {
    for i := 0; i < 5; i++ {
        fmt.Println(s)
    }
}

func main() {
    go say("world")
    say("hello")
    time.Sleep(time.Second)
}

这次执行结果如下:

hello
hello
hello
hello
hello
world
world
world
world
world

main函数最后加了一行time.Sleep(time.Second),这样main goroutine就可以把CPU的执行权让出来,所以另外一个goroutine得以运行。

参考资料:
(1)Go 1.5 GOMAXPROCS Default
(2)Some questions about goroutine
(3)Why is time.sleep required to run certain goroutines

Go语言“:=”用法浅析

Go语言中可以使用“:=”为一个新的变量完成声明以及初始化的工作,如下例所示:

i := 1

等价于:

var i = 1

要注意语句中没有变量类型,不是var i int = 1

:=”不能重新声明一个已经声明过的变量,如下例所示:

package main

import "fmt"

func main() {
    var i = 1

    i := 2

    fmt.Println(i)
}

编译结果:

C:/Go\bin\go.exe run C:/Work/go_work/Hello.go
# command-line-arguments
.\Hello.go:8: no new variables on left side of :=

错误的原因是变量被重复声明了。

但使用“:=”为多个变量赋值时,如果引入了至少一个新的变量,编译是可以通过的,如下例所示:

package main

import "fmt"

func main() {
    var i = 1

    i, err := 2, false

    fmt.Println(i, err)
}

编译执行:

C:/Go\bin\go.exe run C:/Work/go_work/Hello.go
2 false

要注意这个时候,并没有重新声明“i”变量,只是为之前声明的“i”变量赋值。

:=”只能用在函数体中。它的一个重要用途是用在“if”,“for”和“switch”语句的初始化,使变量成为一个“临时变量”,也就是变量的作用域仅限于这条语句。如下例所示:

package main

import "fmt"

func main() {

    for  j := 3; j <= 5; j++ {
        fmt.Println(j)
    }

    fmt.Println(j)
}

编译结果:

C:/Go\bin\go.exe run C:/Work/go_work/Hello.go
# command-line-arguments
.\Hello.go:11: undefined: j

j”的声明作用域仅限于“for”语句,所以编译第二个打印语句会出错。

参考资料:
(1)Short variable declarations;
(2)Assignment operator in Go language

GOROOT和GOPATH

GOROOT指向Go开发包的安装目录。从Go 1.0开始,不必显示地设置GOROOT环境变量。Windows安装包将会自动设置GOROOT,默认装在C:\Go

GOROOT=C:\Go\

而在*nix环境下,下载Go安装包并解压在/usr/local/目录下,然后把/usr/local/go/bin加入PATH环境变量即可:

export PATH=$PATH:/usr/local/go/bin  

如果Go安装包没有安装在默认的目录下(WindowsC:\Go*nix/usr/local/go),则需要手动设置GOROOT,举个例子(*nix):

export GOROOT=$HOME/go

GOPATH指定了Go工程目录,包含srcpkgbin三个子目录。这是开发Go程序时,唯一需要显示设置的环境变量。当使用go get目录下载Go第三方程序包时,也会安装在这个目录下。此外,为了方便,要记得把$GOPATH/bin也加到PATH环境变量:

export PATH=$PATH:$GOPATH/bin  

另外,根据这个帖子的推荐,设置一个GoPATH足够了。

参考资料:

1. Easy Go Programming Setup for Windows
2. You don’t need to set GOROOT, really
3. How to Write Go Code;
4. Go Getting Started

第一次北京Golang爱好者聚会小记

昨天参加了第一次北京Golang爱好者聚会(活动可以参考我之前的文章),总体来说办的很成功。讲师们分享的话题很不错,大家提问也很踊跃。最后大家也都拿到了纪念品,很happy!

第一个话题是GolangWeb开发方面的分享以及做的开源项目。我对Web编程不是很懂,但是开源项目演示效果很不错,值得以后关注一下。
第二个话题是使用Golang的经验分享,很详细。尤其是对于Golang新手,值得一听。
第三个话题是对Golang并发编程的经验分享,也不错,对Golang内部机制感兴趣的朋友,可以仔细研究一下。
第四个话题其实和Golang没什么关系,分享了容器技术的一些原理和实现。
第五个话题是介绍用Golang实现的开源项目codis,碰巧我对Redis有一些了解,所以对这一部分最感兴趣,也和讲师讨论了一下。对Redis感兴趣的朋友,可以关注一下这个项目。
最后一个话题是关于OpenAPM的,这个只是泛泛地提了一下,没什么实质性的内容。

我目前对Golang的感觉是:有一些公司开始在用,但短期内可能不会大规模增长。原因如下:
(1)现有的大量系统都是用老牌语言开发的,用Golang重新开发成本太大,风险太高。很多公司不愿冒险;
(2)目前市场上Golang人才毕竟相对较少。一旦有项目组人员流失,短期内有可能会招不到合适的人选;
(3)Golang语言自身的一些因素,比如gc机制,也许也是一个原因。

我不是语言专家,也没资格对任何语言评头论足。我只是建议大家如果感兴趣,可以去看看Golang。其实了解一门语言最大的收获应该是了解它的实现机理,了解它为什么要这样设计,也许这些内容会给我们日常的工作带来一些启迪。

北京Golang爱好者聚会活动

Go友团网站昨晚发布了北京Golang爱好者聚会活动:Go语言技术聚会——北京(2015年1月),感兴趣的朋友可以关注一下。

个人感觉Go语言写的code确实很清爽,是一门值得学习的语言。其实目前很多公司都已经开始使用Go语言了,不过似乎Gopher们交流真是不多,至少感觉在北京是这样的。我印象中只有前一段时间并发编程网举办的技术沙龙上七牛的工程师分享了一篇Go的讲座。

有兴趣的同学关注一下这个活动吧,也关注一下Go语言。毕竟老牌黑客Rob PikeC语言发明者Ken Thompson弄出来的东西应该是有它独到之处吧。

P.S,昨天是Go语言5周岁生日,大家可以阅读一下官网上的文章:Half a decade with Go,了解一下Go的过去,现在和将来。

liteIDE写Golang程序引用外面的package不能自动补全的问题

在使用liteIDE开发Golang程序时,会出现Golang自带的package可以自动补全,而引用外面的package则不能自动补全。今天终于在stackoverflow(http://stackoverflow.com/questions/19876902/liteide-no-autocomplete)找到了答案:在使用外面的package时,应该把package安装成功(使用go get, go install命令),才可以使用自动补全。