Go语言实践技巧(2)——package定义和初始化

(1)一个文件夹下的所有文件必须属于同一个package,一个好的规则是package和文件夹名字相同。此外,要注意import语句所指定的是寻找packagepath,而不是package的名字。关于这个话题讨论,请参见Is the package name must same with name folder name?

另外,要注意import的语法(参考这里):

import packagename importpath

举例如下:

Import declaration          Local name of Sin

import   "lib/math"         math.Sin
import m "lib/math"         m.Sin
import . "lib/math"         Sin

在第二种情况下,mpackage名字;第三种情况下,使用math package可以省略package名字。

 

(2)

import (
_ "github.com/xxx"
)

这种做法可以保证”github.com/xxx“这个package的初始化操作完成(这个package中所有init函数都执行),即使在当前文件中没有显式地使用到这个package

Go语言实践技巧(1)——变量定义

Go语言变量定义的一个好的实践技巧是:

(1)如果变量声明时会被赋给数据类型的非“0”值,则使用“:=”这种方式:

a := 3
b := fun1()

(2)否则,因为所有变量声明时都会初始化为数据类型的“0”值,则使用var关键字定义这种方式:

var a int;

Go语言字符串浅析

Go语言中,字符串就是一个只读(read-only)的,可以包含任意字节(byte)的切片(slice)。由于Go源码文件都是使用UTF-8编码的,所以直接在源码中输入的字符串也都是用UTF-8编码的(前提是字符串里没有byte-level escapes,即字节层面上的转义)。请看下面这个例子:

package main
import "fmt"

func main() {
    s := "日志log"

    fmt.Println(len(s))
}

执行结果:

9

s是一个UTF-8编码的字符串,“日志”两个汉字各占3个字节,所以一共是9个字节。

再看一个例子:

package main
import "fmt"

func main() {
    s:="\xFF\xFF"

    fmt.Println(len(s))
}

执行结果:

2

虽然0xFF不是一个合法的UTF-8编码字符,但是因为字符串可以包含任意字节,所以s仍是一个合法的字符串。

Go语言引入rune类型(实际就是int32)来表示字符(character),“日志log”这个字符串包含了5个字符,9个字节。 可以用for range循环来遍历UTF-8编码字符串的每个字符:

package main
import "fmt"

func main() {
    s := "日志log"
    for index, runeValue := range s {
        fmt.Printf("%#U starts at byte position %d\n", runeValue, index)
    }
}

执行结果:

U+65E5 '日' starts at byte position 0
U+5FD7 '志' starts at byte position 3
U+006C 'l' starts at byte position 6
U+006F 'o' starts at byte position 7
U+0067 'g' starts at byte position 8

参考资料:
Strings, bytes, runes and characters in Go

Go语言数组浅析

先看一个简单的数组定义:

var array [3]int

上面语句定义了变量array,这个变量是一个包含3个整数的数组类型。可以使用Go语言内置的len函数得到数组长度:

package main

import (
    "fmt"
)

func main() {
    var array [3]int
    fmt.Println(len(array))
}

执行以上程序结果:

3

需要注意的是,在Go语言中,数组的长度是数组类型的一部分,它必须是一个常量表达式,即长度的值在程序编译时是可以得到的。请看下面这个程序:

package main

import (
    "fmt"
)

func print_array(array []int) {
    fmt.Printf("In function, array is %v\n", array)
}

func main() {
    var array [3]int

    print_array(array )
}

编译这个程序会产生下面的错误:

main.go:14: cannot use array (type [3]int) as type []int in argument to print_array

Go语言要求传入函数的形参和实参的类型必须是完全匹配的,而array的类型是[3]int,不是[]int,所以编译会出错。

Go语言函数参数都是传值调用,所以如果函数的参数是数组的话,那么实际传入的将是原数组的拷贝。请看这个例子:

package main

import (
    "fmt"
)

func change_array(array [3]int) {
    for i, _ := range array {
        array[i] = 1
    }
    fmt.Printf("In function, array address is %p, value is %v\n", &array, array)
}

func main() {
    var array [3]int

    fmt.Printf("Original array address is %p, value is %v\n", &array, array)
    change_array(array)
    fmt.Printf("Changed array address is %p, value is %v\n", &array, array)
}

执行结果:

Original array address is 0xc082006560, value is [0 0 0]
In function, array address is 0xc0820065e0, value is [1 1 1]
Changed array address is 0xc082006560, value is [0 0 0]

可以看到,在change_array函数中操作的数组地址并不是main函数中的数组地址,而main函数中的数组的值在调用change_array函数前后也没有发生变化。

如果数组的元素类型是可比较的,那么数组类型就是可比较的,即可以使用“==”和“!=”运算符对数组进行运算。 请看下例:

package main
import "fmt"

func main()  {
    a := [...]int{2, 1}
    b := [...]int{1, 2}
    fmt.Println(a == b, a != b)
}

执行结果如下:

false true

 

Go语言runtime.Gosched()函数浅析

Go语言runtime.Gosched()函数的官方文档如下:

func Gosched

    func Gosched()
Gosched yields the processor, allowing other goroutines to run. It does not suspend the current goroutine, so execution resumes automatically.

这个函数的作用是让当前goroutine让出CPU,好让其它的goroutine获得执行的机会。同时,当前的goroutine也会在未来的某个时间点继续运行。

请看下面这个例子(show.go):

package main

import (
    "fmt"
)

func showNumber (i int) {
    fmt.Println(i)
}

func main() {

    for i := 0; i < 10; i++ {
        go showNumber(i)
    }

    fmt.Println("Haha")
}

执行结果如下:

[root@Fedora goEx]# go build show.go
[root@Fedora goEx]# ./show
Haha

没有打印出数字,可以看到goroutine没有获得机会运行。

修改代码:在main函数中加上runtime.Gosched()

package main

import (
    "fmt"
    "runtime"
)

func showNumber (i int) {
    fmt.Println(i)
}

func main() {

    for i := 0; i < 10; i++ {
        go showNumber(i)
    }

    runtime.Gosched()
    fmt.Println("Haha")
}

编译运行:

[root@Fedora goEx]# go build show.go
[root@Fedora goEx]# ./show
0
1
2
3
4
5
6
7
8
9
Haha

可以看到goroutine获得了运行机会,打印出了数字。

参考资料:
(1)Gosched
(2)What exactly does runtime.Gosched do?

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