132014
 

热爱Go语言,一直使用着、关注着。那么如何获取Go最新动态,使用它最新的特性能?

1、获取最新动态

获取Go语言的最新动态有以下几种方法。

1.1 最直接最原始的方式 —— 官方源码库(hg clone https://code.google.com/p/go/),即 tip。关注Go开发者们提交代码的注释、代码diff。

1.2 关注 golang-dev 讨论组。Go开发者会在这上面讨论Go语言的开发

1.3 关注 issues,以及代码review

1.4 通过 Go dashboard 了解 Go 某个版本的 issues 解决情况(链接最后修改为相应版本即可)

1.5 Go官方网站对应的 tip 版

1.6 关注国内 Go 社区的一些信息

2、使用最新特性

在新版本还未发布时,可能会有些新特性提前公布出来,如果想试验下,就需要安装 tip 版 Go了。

clone Go tip 代码,Windows 下建议使用 TortoiseHg,管理、查看都很方便。

2.1、编译 tip 版本

使用 tip 版本,只能自己编译。在 Unix 下,编译很方便,而在 Windows 下相对就麻烦些,需要安装 MinGW 这样的工具。MinGW 比 Cygwin 轻,下载地址:去下载

安装好 MinGW后(保证命令行能使用 gcc),可以跟 Unix 下一样编译 Go 了。多版本并存问题,请参考《Go语言:安装多版本》。

然后,编译的时候可能会遇到如下问题:

*** failed to import extension codereview from c:\go\lib\codereview\
codereview.py: No module named HTMLParser

咋一看,以为没有安装 Python的原因。但是安装 Python 后,问题依旧

2.2、寻找答案的途径

一般的,我们遇到问题会上谷歌、百度之类的搜索引擎查。对于 Go 语言,目前还比较小众,有些问题可能搜索引擎找不到答案。因此,我们可以考虑其他途径。

个人建议遇到 Go 方面的问题,可以考虑先到 golang-nuts 讨论组去搜索,一般都会找到答案,如果找不到,可以在上面描述你的问题,很快就会有人解答的。

当然,如果你的英文不太好,或苦于翻墙费劲,可以在国内的社区提问,比如:Go语言学习园地, 会尽快得到答复的。

针对这个问题,以 No module named HTMLParser 为关键词,在 golang-nuts 上搜索,能较快找到答案:codereview extension under Windows/Mingw Mercurial

1. install mercurial binary package
2. inastall python2.7 binary package
3. copy following modules into the root folder of “library.zip” from python2.7/lib/
markupbase.py
htmlentitydefs.py
HTMLParser.py

其中,安装了 TortoiseHg 后(不需要再安装 mercurial),在其目录中就会有 library.zip

照着做了后,再编译,一切都 OK 了。

2.3、使用新特性

安装了 tip 版,就可以使用 Go 的最新特性了,尽情享受 Go 带给你的快了吧!

注:以上不少网址可能都被墙了,程序员应该学会翻墙!

update

1. 发现了一个 go 源码的 github 只读镜像,代码几乎和官方同步,不用翻墙可以看Go最新变化了。 https://github.com/jnwhiteh/golang

132014
 

在开发或者自己学习Go的过程中,可能会对比不同版本Go语言的特性、性能等,特别是可能想提前用上tip版本的一些特性,这个时候,系统中可能需要多个Go版本。那么该如何处理这个问题呢?

一般地,我觉得有两种处理方法。

1、目录改名法

比如 Go 版本(go1.3)安装在 /usr/local/go 目录中,并配置了 GOROOT(GOROOT=/usr/local/go)和 PATH (PATH=$PATH:$GOROOT/bin),这时,我们想安装一个Go1.2版本,比如放在了 /usr/local/go1.2 中。

通常,我们都在使用 Go1.3,但某个时候,我们想使用 Go1.2,这个时候,可以将 /usr/local/go 改名为 /usr/local/go1.3,同时将 /usr/local/go1.2 改名为 /usr/local/go,这样,Go版本就变成了 1.2 了。有其他版本时,一样处理。

2、不设置GOROOT的方法

最早时,Go需要设置不少环境变量,比如:GOARCH/GOOS/GOBIN/GOROOT 等,特别是GOBIN,网上有不少旧文章讲安装 Go 时,都提到设置这个环境变量,而实际上,如果你设置了GOBIN,使用 go install 时会出现可执行文件被安装到了 GOBIN 目录中,而不是 GOPATH/bin 目录中。

一般地,Go1.0 之后,只需要设置 GOROOT 和 GOPATH 就可以了,我们通过 go env 命令能够看到Go相关的环境变量:

GOARCH=”amd64″
GOBIN=”"
GOCHAR=”6″
GOEXE=”"
GOHOSTARCH=”amd64″
GOHOSTOS=”linux”
GOOS=”linux”
GOPATH=”/root/golang/myproject/”
GORACE=”"
GOROOT=”/usr/local/go”
GOTOOLDIR=”/usr/local/go/pkg/tool/linux_amd64″
CC=”gcc”
GOGCCFLAGS=”-fPIC -m64 -pthread -fmessage-length=0″
CXX=”g++”
CGO_ENABLED=”1″

也就是说,虽然,我们只设置了GOROOT和GOPATH,但实际上,很多其他环境变量也是有值的。实际上,在 《分析源码安装Go的过程》 一文中提到,make.bash 脚本会 export GOROOT,也就是说在安装时,并不需要自己设置 GOROOT,那么安装完成之后呢?可以试试在安装完成之后,不设置GOROOT,看看 go env 的输出,你会发现,GOROOT已经正确设置了。

可见,即使没有主动设置GOROOT,实际上 Go 也能知道 GOROOT 指向哪里,也就是说,主动设置 GOROOT 会覆盖默认的 GOROOT 设置。既然不设置 GOROOT,Go也能正确的找到 Go 安装目录,那么我们为啥还要设置呢?(可能设置的另一个理由是,设置 PATH 时,可以通过 $GOROOT 这种方式引入,改变 GOROOT 就可以起到改变 PATH 的目的)

这样一来,我们安装多个版本的Go,各个版本的GOROOT总是能够指向自己的目录,基于这一点,我们可以做到同时使用多版本。

当多版本时,我们可以将主版本的 bin 目录加入 PATH,以方便直接使用 go 命令。对于其他版本,我们可以通过建立软连接的方式来使用,比如:ln -s /usr/local/go1.2/bin/go /usr/bin/go1.2 (windows 下可以考虑直接改名)(注:Go1.4 将支持 Windows 下创建符号链接,这样可以用 Go 实现在Windows下建立符号链接,而不是改名,详见:《【Go1.4】主要改动》

综上,选择哪种方式根据你的喜好。个人觉得方法2方便些。配置了一次后,直接就可以使用了,而方法1总是得修改目录。

3、我的多版本配置

命令行 go/go1.0/go1.1/go1.2/go1.3/go_tip 分别使用对应的Go版本。(其中,go 使用了 最新稳定版)

Linux下:
多版本
多版本

Windows下:
多版本
多版本

注:quick_tools 下的文件是 通过自己写的 Windows 下创建符号链接工具创建的。

4、参考文献

5、代码下载

Windows 下的 ln 源码下载

二进制程序下载

6、更新说明

1)2014-08-13 初始版本
2)2014-08-15 增加 我的多版本配置 部分

222013
 

Go’s interfaces—static, checked at compile time, dynamic when asked for—are, for me, the most exciting part of Go from a language design point of view. If I could export one feature of Go into other languages, it would be interfaces.

《Go Data Structures: Interfaces》—- Russ Cox

可见interface是go中很重要的一个特性。

在网上有人问:Go语言中接口到底有啥好处,能否举例说明?于是,我考虑以io.Writer接口为例谈谈interface{}

一、io.Writer接口

在go标准库io包中定义了Writer接口:

type Writer interface {
	Write(p []byte) (n int, err error)
}

根据go中接口的特点,所有实现了Write方法的类型,我们都说它实现了io.Writer接口。

二、io.Writer的应用

通常,我们在使用fmt包的时候是使用Println/Printf/Print方法。其实,在fmt包中还有Fprint序列方法,而且,Print序列方法内部调用的是Fprint序列方法。以Fprintln为例看看方法的定义:

func Fprintln(w io.Writer, a ...interface{}) (n int, err error)

方法的第一个参数是io.Writer,也就是说,任何实现了io.Writer接口的类型实例都可以传递进来;我们再看看Println方法内部实现:

func Println(a ...interface{}) (n int, err error) {
	return Fprintln(os.Stdout, a...)
}

我们不妨追溯一下os.Stdout:

Stdout = NewFile(uintptr(syscall.Stdout), "/dev/stdout")

也就是标准输出。
从这里可以看出,os.File也实现了io.Writer,那么,如果第一个参数传递的是一个普通文件,内容便会被输出到该文件。
如果第一个参数传递的是bytes.Buffer,那么,内容便输出到了buffer中。

在写Web程序时,比如:

func Index(rw http.ResponseWriter, req *http.Request) {
	fmt.Fprintln(rw, "Hello, World")
}

这样便把”Hello World”输出给了客户端。

三、关于接口更多学习资料

1、Rob Pike谈Go中的接口

292013
 

原本不打算介绍GOPATH,然而,总是有初学者问一些关于GOPATH的问题,因此在这里再介绍一下GOPATH

GOPATH环境变量用于指定这样一些目录:除$GOROOT之外的包含Go项目源代码和二进制文件的目录。go install和go 工具会用到GOPATH:作为编译后二进制的存放目的地和import包时的搜索路径。

GOPATH是一个路径列表,也就是可以同时指定多个目录。多个目录在Mac和Linux下通过”:”分割;Windows下通过”;”分割。注意,大部分情况下会是第一个路径优先,比如:查找包

对于有些情况,比如,在GOPATH所在的路径之外执行go install会怎么样,可以自己试验一下。

个人建议GOPATH中别设置多个路径,甚至不显示指定GOPATH。可参照《Go项目的目录结构》

有些地方建议在设置了GOPATH之后,将$GOPATH/bin加入PATH中,这样可以方便的运行go install好的二进制程序。然而,当存在GOPATH中存在多个路径时,这种写法只会将最后一个路径跟上bin。在mac或linux下可以通过这种方式解决:
${GOPATH//://bin:}/bin

十二 202012
 

据我所知,目前大部分人都会将main包直接放在src根目录,运行时使用 go run xxx.go 这种方式。当然如果需要生成可执行文件,就通过go build在当前目录生成一个可执行文件。LiteIDE也是这么做的(已经和作者提建议了)。

然而,将main包直接放在src根目录,不能用go install 安装成标准的 src pkg bin这样的结构。如果一个项目中有多个server呢?你这样就没法弄了,得分开成几个项目;然而这几个server之间可能有不少需要共享的东西,这样可能又得增加GOPATH。

另外,虽然main包文件直接在src根目录下可以通过go build生成可执行文件,但默认是生成在当前目录下的(可能污染源码),而不会生成到bin目录(除非自己参数指定)。实际上官方都建议将GOPATH的bin目录加入PATH(当然,好像这么做的不多)

我建议直接通过go install生成二进制文件。即使项目中有多个server(可能还有其他客户端测试工具),二进制文件可能会比较多(即可能多个main包),这个时候,main单独放在目录中很有用。go install效果就有了。

可能有一个疑问:go install需要指定GOPATH,很麻烦。可以使用http://blog.studygolang.com/46.html上介绍的方式。

在golang-nuts上有人讨论了这个问题
Running multi-file main package
有人回复说:go run是给那些需要快速(测试用)和dirty code使用的,这些代码是不会是实际项目,也不会在GOPATH中。

十二 122012
 

说明:作为一门静态语言,似乎支持调试是必须的,而且,Go初学者喜欢问的问题也是:大家都用什么IDE?怎么调试?

其实,Go是为多核和并发而生,真正的项目,你用单步调试,原本没问题的,可能会调出有问题。更好的调试方式是跟PHP这种语言一样,用打印的方式(日志或print)。

当然,简单的小程序,如果单步调试,可以看到一些内部的运行机理,对于学习还是挺有好处的。下面介绍一下用GDB调试Go程序:(目前IDE支持调试Go程序,用的也是GDB。要求GDB 7.1以上)

以下内容来自雨痕的《Go语言学习笔记》(下载Go资源):

默认情况下,编译过的二进制文件已经包含了 DWARFv3 调试信息,只要 GDB7.1 以上版本都可以进行调试。 在OSX下,如无法执行调试指令,可尝试用sudo方式执行gdb。

删除调试符号:go build -ldflags “-s -w”

  • -s: 去掉符号信息。
  • -w: 去掉DWARF调试信息。

关闭内联优化:go build -gcflags “-N -l”

调试相关函数:

  • runtime.Breakpoint():触发调试器断点。
  • runtime/debug.PrintStack():显示调试堆栈。
  • log:适合替代 print显示调试信息。

GDB 调试支持:

  • 参数载入:gdb -d $GCROOT 。
  • 手工载入:source pkg/runtime/runtime-gdb.py。

更多细节,请参考: http://golang.org/doc/gdb

调试演示:(OSX 10.8.2, Go1.0.3, GDB7.5.1)

package main

import (
	"fmt"
	"runtime"
)

func test(s string, x int) (r string) {
	r = fmt.Sprintf("test: %s %d", s, x)
	runtime.Breakpoint()
	return r
}
func main() {
	s := "haha"
	i := 1234
	println(test(s, i))
}

$ go build -gcflags “-N -l” // 编译,关闭内联优化。

$ sudo gdb demo // 启动 gdb 调试器,手工载入 Go Runtime 。
GNU gdb (GDB) 7.5.1
Reading symbols from demo…done.
(gdb) source /usr/local/go/src/pkg/runtime/runtime-gdb.py
Loading Go Runtime support.

(gdb) l main.main // 以 .方式查看源码。
9 r = fmt.Sprintf(“test: %s %d”, s, x)
10 runtime.Breakpoint()
11 return r
12 }
13
14 func main() {
15 s := “haha”
16 i := 1234
17 println(test(s, i))
18 }

(gdb) l main.go:8 // 以 :方式查看源码。
3 import (
4 “fmt”
5 “runtime”
6 )
7
8 func test(s string, x int) (r string) {
9 r = fmt.Sprintf(“test: %s %d”, s, x)
10 runtime.Breakpoint()
11 return r
12 }

(gdb) b main.main // 以 .方式设置断点。
Breakpoint 1 at 0×2131: file main.go, line 14.

(gdb) b main.go:17 // 以 :方式设置断点。
Breakpoint 2 at 0×2167: file main.go, line 17.

(gdb) info breakpoints // 查看所有断点。
Num Type Disp Enb Address What
1 breakpoint keep y 0×0000000000002131 in main.main at main.go:14
2 breakpoint keep y 0×0000000000002167 in main.main at main.go:17

(gdb) r // 启动进程,触发第一个断点。
Starting program: demo
[New Thread 0x1c03 of process 4088]
[Switching to Thread 0x1c03 of process 4088]
Breakpoint 1, main.main () at main.go:14
14 func main() {

(gdb) info goroutines // 查看 goroutines 信息。
* 1 running runtime.gosched
* 2 syscall runtime.entersyscall

(gdb) goroutine 1 bt // 查看指定序号的 goroutine 调用堆栈。
#0 0x000000000000f6c0 in runtime.gosched () at pkg/runtime/proc.c:927
#1 0x000000000000e44c in runtime.main () at pkg/runtime/proc.c:244
#2 0x000000000000e4ef in schedunlock () at pkg/runtime/proc.c:267
#3 0×0000000000000000 in ?? ()

(gdb) goroutine 2 bt // 这个 goroutine 貌似跟 GC 有关。
#0 runtime.entersyscall () at pkg/runtime/proc.c:989
#1 0x000000000000d01d in runtime.MHeap_Scavenger () at pkg/runtime/mheap.c:363
#2 0x000000000000e4ef in schedunlock () at pkg/runtime/proc.c:267
#3 0×0000000000000000 in ?? ()

(gdb) c / / 继续执行,触发下一个断点。
Continuing.
Breakpoint 2, main.main () at main.go:17
17! ! println(test(s, i))

(gdb) info goroutines // 当前 goroutine 序号为 1。
* 1 running runtime.gosched
2 runnable runtime.gosched

(gdb) goroutine 1 bt // 当前 goroutine 调用堆栈。
#0 0x000000000000f6c0 in runtime.gosched () at pkg/runtime/proc.c:927
#1 0x000000000000e44c in runtime.main () at pkg/runtime/proc.c:244
#2 0x000000000000e4ef in schedunlock () at pkg/runtime/proc.c:267
#3 0×0000000000000000 in ?? ()

(gdb) bt // 查看当前调⽤堆栈,可以与当前 goroutine 调用堆栈对比。
#0 main.main () at main.go:17
#1 0x000000000000e44c in runtime.main () at pkg/runtime/proc.c:244
#2 0x000000000000e4ef in schedunlock () at pkg/runtime/proc.c:267
#3 0×0000000000000000 in ?? ()

(gdb) info frame // 堆栈帧信息。
Stack level 0, frame at 0x442139f88:
rip = 0×2167 in main.main (main.go:17); saved rip 0xe44c
called by frame at 0x442139fb8
source language go.
Arglist at 0x442139f28, args:
Locals at 0x442139f28, Previous frame’s sp is 0x442139f88
Saved registers:
rip at 0x442139f80

(gdb) info locals // 查看局部变量。
i = 1234
s = “haha”

(gdb) p s // 以 Pretty-Print 方式查看变量。
$1 = “haha”

(gdb) p $len(s) // 获取对象长度($cap)
$2 = 4

(gdb) whatis i // 查看对象类型。
type = int

(gdb) c // 继续执行,触发 breakpoint() 断点。
Continuing.
Program received signal SIGTRAP, Trace/breakpoint trap.
runtime.breakpoint () at pkg/runtime/asm_amd64.s:81
81 RET

(gdb) n // 从 breakpoint() 中出来,执行源码下一行代码。
main.test (s=”haha”, x=1234, r=”test: haha 1234″) at main.go:11
11 return r

(gdb) info args // 从参数信息中,我们可以看到命名返回参数的值。
s = “haha”
x = 1234
r = “test: haha 1234″

(gdb) x/3xw &r // 查看 r 内存数据。(指针 8 + 长度 4)
0x442139f48: 0×42121240 0×00000000 0x0000000f
(gdb) x/15xb 0×42121240 // 查看字符串字节数组
0×42121240: 0×74 0×65 0×73 0×74 0x3a 0×20 0×68 0×61
0×42121248: 0×68 0×61 0×20 0×31 0×32 0×33 0×34

(gdb) c // 继续执行,进程结束。

Continuing.
test: haha 1234
[Inferior 1 (process 4088) exited normally]

(gdb) q // 退出 GDB。

十二 072012
 

1、map数据类型初始化

两种方式:map[string]string{}或make(map[string]string)

2、未初始化的map是nil,它与一个空map基本等价,只是nil的map不允许往里面添加值。(A nil map is equivalent to an empty map except that no elements may be added)

因此,map是nil时,取值是不会报错的(取不到而已),但增加值会报错。

其实,还有一个区别,delete一个nil map会panic,但是delete 空map是一个空操作(并不会panic)(这个区别在最新的Go tips中已经没有了,即:delete一个nil map也不会panic)

3、通过fmt打印map时,空map和nil map结果是一样的,都为map[]。所以,这个时候别断定map是空还是nil,而应该通过map == nil来判断。

Request中的Form字段就是如此,在没有直接或间接调用ParseForm()时,Form其实是nil,但是,你如果println出来,却是map[],可能有些困惑。通过跟踪源码可以发现,Form根本没有初始化。而在FormValue()方法中会判断Form是否为nil,然后决定是否调用ParseForm()方法,当然,你也可以手动调用ParseForm()方法。

十二 052012
 

项目目录结构如何组织,一般语言都是没有规定。但Go语言这方面做了规定,这样可以保持一致性

1、一般的,一个Go项目在GOPATH下,会有如下三个目录:

|--bin
|--pkg
|--src

其中,bin存放编译后的可执行文件;pkg存放编译后的包文件;src存放项目源文件。一般,bin和pkg目录可以不创建,go命令会自动创建(如 go install),只需要创建src目录即可。
对于pkg目录,曾经有人问:我把Go中的包放入pkg下面,怎么不行啊?他直接把Go包的源文件放入了pkg中。这显然是不对的。pkg中的文件是Go编译生成的,而不是手动放进去的。(一般文件后缀.a)
对于src目录,存放源文件,Go中源文件以包(package)的形式组织。通常,新建一个包就在src目录中新建一个文件夹。

2、举例说明

比如:我新建一个项目,test,开始的目录结构如下:

test--|--src

为了编译方便,我在其中增加了一个install文件,目录结构:

test/
|-- install
`-- src

其中install的内容如下:(linux下)

#!/usr/bin/env bash

if [ ! -f install ]; then
echo 'install must be run within its container folder' 1>&2
exit 1
fi

CURDIR=`pwd`
OLDGOPATH="$GOPATH"
export GOPATH="$CURDIR"

gofmt -w src

go install test

export GOPATH="$OLDGOPATH"

echo 'finished'

之所以加上这个install,是不用配置GOPATH(避免新增一个GO项目就要往GOPATH中增加一个路径)

接下来,增加一个包:config和一个main程序。目录结构如下:

test
|-- install
`-- src
    |-- config
    |   `-- config.go
    `-- test
        `-- main.go

注意,config.go中的package名称必须最好和目录config一致,而文件名可以随便。main.go表示main包,文件名建议为main.go。(注:不一致时,生成的.a文件名和目录名一致,这样,在import 时,应该是目录名,而引用包时,需要包名。例如:目录为myconfig,包名为config,则生产的静态包文件是:myconfig.a,引用该包:import “myconfig”,使用包中成员:config.LoadConfig()

config.go和main.go的代码如下:
config.go代码

package config

func LoadConfig() {

}

main.go代码

package main

import (
	"config"
	"fmt"
)

func main() {
	config.LoadConfig()
	fmt.Println("Hello, GO!")
}

接下来,在项目根目录执行./install

这时候的目录结构为:

test
|-- bin
|   `-- test
|-- install
|-- pkg
|   `-- linux_amd64
|       `-- config.a
`-- src
    |-- config
    |   `-- config.go
    `-- test
        `-- main.go
(linux_amd64表示我使用的操作系统和架构,你的可能不一样)

其中config.a是包config编译后生成的;bin/test是生成的二进制文件

这个时候可以执行:bin/test了。会输出:Hello, GO!

3、补充说明

1)包可以多层目录,比如:net/http包,表示源文件在src/net/http目录下面,不过源文件中的包名是最后一个目录的名字,如http
而在import包时,必须完整的路径,如:import “net/http”

2)有时候会见到local import(不建议使用),语法类似这样:

import “./config”

当代码中有这样的语句时,很多时候都会见到类似这样的错误:local import “./config” in non-local package

我所了解的这种导入方式的使用是:当写一个简单的测试脚本,想要使用go run命令时,可以使用这种导入方式。
比如上面的例子,把test/main.go移到src目录中,test目录删除,修改main.go中的import “config”为import “./config”,然后可以在src目录下执行:go run main.go

可见,local import不依赖于GOPATH

4、Windows下的install.bat

@echo off

setlocal

if exist install.bat goto ok
echo install.bat must be run from its folder
goto end

: ok

set OLDGOPATH=%GOPATH%
set GOPATH=%~dp0

gofmt -w src

go install test

:end
echo finished

注,冒号和ok之间不应该有空格,但是放在一起总是会被wordpress转成一个表情。汗……

5、更新日志

1)2012-12-05 发布
2)2013-04-13 修正:目录名可以和包名不同,但建议一致;将make文件名改为install