在 Golang 中,我们的代码在某个包下。main 包是程序执行的入口。在 Go 中有很多内置的包,例如我们之前用过的 fmt 包。
Go 的包机制是大型软件的基础,能够将大型的工程分解成小部分。
10.1 安装一个包
go get // 例子 go get github.com/satori/go.uuid //安装的包保存在GOPATH的环境中,你可以在 $GOPATH/pkg 路径下看到安装的包。
10.2 创建一个自定义包
首先创建一个文件夹 custom_package
:
> mkdir custom_package > cd custom_package #创建自定义包的第一步是创建一个和包名相同的文件夹。 #我们要创建person包,因此我们在custom_package文件下创建person文件夹: > mkdir person > cd person
在该路径下创建一个文件 person.go:
package person func Description(name string) string { return "The person name is: " + name } func secretName(name string) string { return "Do not share" }
现在我们来安装这个包,这样我们就可以导入和使用它了:
> go install
接下来我们返回 custom_package 文件夹中,创建一个 main.go
:
package main import ( "custom_package/person" "fmt" ) func main() { p := person.Description("Milap") fmt.Println(p) } // => The person name is: Milap
在这里,我们可以导入之前创建的包 person,需要注意的是在 person 包中函数 secretName 不能被访问,这是因为 Go 中小写字母开头的函数是私有函数。
10.3 生成包文档
Golang 中有内置的功能支持包文档。运行下面的命令将生成文档:
godoc person Description
这将会为 Description 函数生成文档,想要在 Web 服务器上查看文档需要运行下面的命令:
godoc -http=":8080" #现在打开链接 http://localhost:8080/pkg/ 将会看到我们刚才看到的文档。
10.4 Go 中内置的包
10.4.1 fmt
fmt 包实现可标准的 I/O 函数,我们在之前的包中用过其中的打印输出函数。
10.4.2 json
Golang 中另一个内置的重要包的是 json
,它能够对 JSON 进行编解码。
编码:
package main import ( "encoding/json" "fmt" ) func main() { mapA := map[string]int{"apple": 5, "lettuce": 7} mapB, _ := json.Marshal(mapA) fmt.Println(string(mapB)) }
解码:
package main import ( "encoding/json" "fmt" ) type response struct { PageNumber int `json:"page"` Fruits []string `json:"fruits"` } func main() { str := `{"page": 1, "fruits": ["apple", "peach"]}` //"{\"page\": 1, \"fruits\": [\"apple\", \"peach\"]}" res := response{} json.Unmarshal([]byte(str), &res) fmt.Println(res.PageNumber) } -------------------- > Output: command-line-arguments 1 > Elapsed: 0.877s > Result: Success
解码的时候使用 Unmarshal
方法,第一个参数是 json 字节,第二个参数是要映射的结构体的地址。需要注意的是 json 中的 “page” 对应的是结构体中的 PageNumber。
错误是程序中不应该出现的结果。假设我们编写一个 API
调用外部的服务。这个 API
可能成功也可能失败。当存在错误是,Golang 程序能够识别:
resp, err := http.Get("http://example.com/")
对 API
的 调用可能成功也可能失败,我们可以通过检查错误是否为空来选择处理方式。
package main import ( "fmt" "net/http" ) func main() { resp, err := http.Get("http://commandnotfound_111.com/") if err != nil { fmt.Println(err) return } fmt.Println(resp) } ----------- > Output: command-line-arguments Get http://commandnotfound_111.com/: dial tcp: lookup commandnotfound_111.com: no such host > Elapsed: 1.720s > Result: Success
11.1 从函数中返回自定义错误
当我们在自定义函数是,某些情况下会产生错误。我们可以使用 error
对象返回这些错误:
package main import ( "errors" "fmt" ) func Increment(n int) (int, error) { if n < 0 { // return error object return 0, errors.New("math: cannot process negative number") } return (n + 1), nil } func main() { num := -5 if inc, err := Increment(num); err != nil { fmt.Printf("Failed Number: %v, error message: %v", num, err) } else { fmt.Printf("Incremented Number: %v", inc) } } -------- Failed Number: -5, error message: math: cannot process negative number > Elapsed: 0.867s > Result: Success
Go 内置的包,外部的包都有处理错误的机制。因此我们调用的函数都有可能产生错误。这些错误不应该忽略而是应该向上面的例子那样被优雅的处理。
11.2 Panic
Panic 是程序运行中突然产生未经处理的异常。在 Go 中,panic 不是合理处理异常的方式,推荐使用 error 对象代替。当 Panic 产生时,程序将会暂停运行。当 panic 被 defer 之后,程序才能继续运行。
package main import "fmt" func main() { f() fmt.Println("Returned normally from f.") } func f() { defer func() { if r := recover(); r != nil { fmt.Println("Recovered in f", r) } }() fmt.Println("Calling g.") g(0) fmt.Println("Returned normally from g.") } func g(i int) { if i > 3 { fmt.Println("Panicking!") panic(fmt.Sprintf("%v", i)) } defer fmt.Println("Defer in g", i) fmt.Println("Printing in g", i) g(i + 1) } -------------- > Output: Calling g. Printing in g 0 Printing in g 1 Printing in g 2 Printing in g 3 Panicking! Defer in g 3 Defer in g 2 Defer in g 1 Defer in g 0 Recovered in f 4 Returned normally from f. > Elapsed: 3.772s > Result: Success
11.3 Defer
Defer 在函数结尾一定会执行。在上面的函数中,使用 panic()
暂停程序的运行,defer 语句使程序执行结束时使用改行。Defer 也可以用作我们想要在函数的结尾执行的语句,例如关闭文件。
Golong 使用轻量级线程 Go routies 支持并发。
12.1 Go routine
Go routine 是能够并行运行的函数。创建 Go routine 非常简单,只需要在函数前添加关键字 go,这样函数就能够并行运行了。Go routines 是轻量级的,我们能够创建上千个 Go routines。例如:
package main import ( "fmt" "time" ) func main() { go c() fmt.Println("I am main") time.Sleep(time.Second * 3) } func c() { time.Sleep(time.Second * 2) fmt.Println("I am concurrent") } //=> I am main //=> I am concurrent
但上述代码,其实有一个坑,我们后期在GO并发中,会详细讲解。
上面的例子中,函数 c 是 Go routine,能够并行运行。我们想要在多线程中共享资源,但是 Golang 并不支持。因为这会导致死锁和资源等待。Go 提供了另一种共享资源的方式:channel
。
12.2 Channels
多个 goroutine 中,Go 语言使用通道(channel)进行通信,通道是一种内置的数据结构,可以让用户在不同的 goroutine 之间同步发送具有类型的消息。这让编程模型更倾向于在 goroutine 之间发送消息,而不是让多个 goroutine 争夺同一个数据的使用权。
程序可以将需要并发的环节设计为生产者模式和消费者的模式,将数据放入通道。通道的另外一端的代码将这些数据进行并发计算并返回结果,如下图所示。
Go 语言通过通道可以实现多个 goroutine 之间内存共享。
我们可以使用 Channel 在两个 Go routine 之间传递数据。创建 channel 之前需要制定接受的数据类型。例如我们创建了一个接受 string 类型的 channel。
c := make(chan string)
有了这个 channel
之后,我们可以通过这个 channel
发送和接收 string
类型的数据。
package main import "fmt" func main() { c := make(chan string) go func() { c <- "hello" }() msg := <-c fmt.Println(msg) } //=>"hello"
12.3 One way channel
有些情况下,我们希望 Go routine 通过 channel 接收数据,但不发送数据,反之亦然。这时候我们可以创建一个 one-way channel
。例如:
package main import ( "fmt" ) func main() { ch := make(chan string) go sc(ch) fmt.Println(<-ch) } func sc(ch chan<- string) { ch <- "hello" }
上面例子中,sc 是一个 Go routine 只能给 channel 发送数据而不能接受数据。
在 Golang 中可以创建 buffered channel,当 buffer 满的时候,发送数据给 channel 将会被阻塞。例如:
package main import "fmt" func main() { ch := make(chan string, 2) ch <- "hello" ch <- "world" ch <- "!" // extra message in buffer fmt.Println(<-ch) } ------------------- #Output: command-line-arguments fatal error: all goroutines are asleep - deadlock! goroutine 1 [chan send]: main.main() exit status 2 > Elapsed: 0.849s > Result: Error
如果看到这里,那么恭喜你,你已经对 Go 有了很好的理解。
我们学习 Golang 以下的主要模块和特性: