也许是最简洁版本,一篇文章上手Go语言(下)

| 选择喜欢的代码风格  

10.包


在 Golang 中,我们的代码在某个包下。main 包是程序执行的入口。在 Go 中有很多内置的包,例如我们之前用过的 fmt 包。

Go 的包机制是大型软件的基础,能够将大型的工程分解成小部分。

Robert Griesemer

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。

11.错误处理


错误是程序中不应该出现的结果。假设我们编写一个 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 也可以用作我们想要在函数的结尾执行的语句,例如关闭文件。

12. 并发


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 发送数据而不能接受数据。

13. 使用 selecct 优化多个 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 有了很好的理解。

14. Golang 学习入门总结


我们学习 Golang 以下的主要模块和特性:

  • 变量,数据类型
  • Array、Slices 和 Map
  • 函数
  • 循环和条件语句
  • 指针
  • 方法、结构体和接口
  • 错误处理
  • 并发 —— Go routines 和 channels
 

Go 扩展阅读:




发表评论