Go语言实战-7-8章

第 7 章 并发模式

  1. runner 包用于展示如何使用通道来监视程序的执行时间,如果程序运行时间太长,也可以用 runner 包来终止程序。当开发需要调度后台处理任务的程序的时候,这种模式会很有用。这个程序可能会作为 cron 作业执行,或者在基于定时任务的云环境(如 iron.io)里执行。

  2. 这是一个名为 New 的工厂函数

1
2
3
4
5
func New(d *time.Duration) *Runner{
return &Runner{
interrupt: make(chan os.Signal, 1),
}
}

通道 interrupt 被初始化为缓冲区容量为 1 的通道。这可以保证通道至少能接收一个来自语言运行时的 os.Signal 值,确保语言运行时发送这个事件的时候不会被阻塞。如果 goroutine没有准备好接收这个值,这个值就会被丢弃。例如,如果用户反复敲 Ctrl+C 组合键,程序只会在这个通道的缓冲区可用的时候接收事件,其余的所有事件都会被丢弃。

下面与 Runner 有关的方法:

1
2
3
4
5
6
7
8
9
func (r *Runner) goInterrupt() bool{
select {
signal.Stop(r.interrupt)
return true
// 继续正常执行
default:
return false
}
}

default 分支会将接收 interrupt 通道的阻塞调用转变为非阻塞的。

  1. pool包用于展示如何使用有缓冲的通道实现资源池,来管理可以在任意数量的goroutine之间共享及独立使用的资源。

  2. work 包的目的是展示如何使用无缓冲的通道来创建一个 goroutine 池,这些 goroutine 执行并控制一组工作,让其并发执行。在这种情况下,使用无缓冲的通道要比随意指定一个缓冲区大小的有缓冲的通道好。无缓冲的通道保证两个 goroutine 之间的数据交换。这种使用无缓冲的通道的方法允许使用者知道什么时候 goroutine 池正在执行工作,而且如果池里的所有 goroutine 都忙,无法接受新的工作的时候,也能及时通过通道来通知调用者。使用无缓冲的通道不会有工作在队列里丢失或者卡住,所有工作都会被处理。

  3. 有缓冲的通道可以用来管理一组可复用的资源。
    语言运行时会处理好通道的协作和同步。
    使用无缓冲的通道来创建完成工作的 goroutine 池。
    任何时间都可以用无缓冲的通道来让两个 goroutine 交换数据,在通道操作完成时一定保证对方接收到了数据。

第8章 标准库

  1. 什么是 Go 标准库?为什么这个库这么重要?Go 标准库是一组核心包,用来扩展和增强语
    言的能力。这些包为语言增加了大量不同的类型。开发人员可以直接使用这些类型,而不用再写
    自己的包或者去下载其他人发布的第三方包。由于这些包和语言绑在一起发布,它们会得到以下
    特殊的保证:
    每次语言更新,哪怕是小更新,都会带有标准库;
    这些标准库会严格遵守向后兼容的承诺;
    标准库是 Go 语言开发、构建、发布过程的一部分;
    标准库由 Go 的构建者们维护和评审;
    每次 Go 语言发布新版本时,标准库都会被测试,并评估性能。

  2. 准库里包含众多的包,不可能在一章内把这些包都讲一遍。目前,标准库里总共有超过100 个包,这些包被分到 38 个类别里

  3. 不管用什么方式安装 Go,标准库的源代码都会安装在$GOROOT/src/pkg 文件夹中。

  4. 作为 Go 发布包的一部分,标准库的源代码是经过预编译的。这些预编译后的文件,称作归档文件(archive file),可以 在$GOROOT/pkg 文件夹中找到已经安装的各目标平台和操作系统的归档文件。

  5. 归档文件是特殊的 Go 静态库文件,由 Go 的构建工具创建,并在编译和链接最终程序时被使用。归档文件可以让构建的速度更快。但是在构建的过程中,没办法指定这些文件,所以没办法与别人共享这些文件。Go 工具链知道什么时候可以使用已有的.a 文件,什么时候需要从机器上的源代码重新构建。

  6. 在 UNIX 里,日志有很长的历史。这些积累下来的经验都体现在 log 包的设计里。传统的CLI(命令行界面)程序直接将输出写到名为 stdout 的设备上。所有的操作系统上都有这种设备,这种设备的默认目的地是标准文本输出。默认设置下,终端会显示这些写到 stdout 设备上的文本。这种单个目的地的输出用起来很方便,不过你总会碰到需要同时输出程序信息和输出执行细节的情况。这些执行细节被称作日志。当想要记录日志时,你希望能写到不同的目的地,这样就不会将程序的输出和日志混在一起了。为了解决这个问题,UNIX 架构上增加了一个叫作 stderr 的设备。这个设备被创建为日志的默认目的地。这样开发人员就能将程序的输出和日志分离开来。如果想在程序运行时同时看到程序输出和日志,可以将终端配置为同时显示写到 stdout 和 stderr 的信息。不过,如果用户的程序只记录日志,没有程序输出,更常用的方式是将一般的日志信息写到 stdout,将错误或者警告信息写到 stderr。

  7. 通常程序会在这个 init()函数里配置日志参数

  8. 设置了一个字符串,作为每个日志项的前缀。这个字符串应该是能让用户从一般的程序输出中分辨出日志的字符串。传统上这个字符串的字符会全部大写。

  9. 操作符<<对左边的操作数执行按位左移操作

  10. Panic 系列函数用来写日志消息,然后触发一个 panic。除非程序执行 recover 函数,否则会导致程序打印调用栈后终止。Print 系列函数是写日志消息的标准方法。log 包有一个很方便的地方就是,这些日志记录器是多 goroutine 安全的。这意味着在多个goroutine 可以同时调用来自同一个日志记录器的这些函数,而不 会有彼此间的写冲突。标准日志记录器具有这一性质,用户定制的日志记录器也应该满足这一性质。

  11. 记录不同的日志

1
2
3
4
Trace *log.Logger // 记录所有日志
Info *log.Logger // 重要的信息
Warning *log.Logger // 需要注意的问题
Error *log.Logger // 非常严重的问题
  1. 如下一个声明和它的作用
1
2
// Discard 是一个 io.Writer,所有的 Write 调用都不会有动作,但是会成功返回
var Discard io.Writer = delNull(0)
  1. 在今天,JSON 远比 XML 流行。这主要是因为与 XML 相比,使用 JSON 需要处理的标签更少。而这就意味着网络传输时每个消息的数据更少,从而提升整个系统的性能。而且,JSON 可以转换为 BSON(Binary JavaScript Object Notation,二进制 JavaScript 对象标记),进一步缩小每个消息的数据长度

  2. 如下你会注意到每个字段最后使用单引号声明了一个字符串。这些字符串被称作标签(tag),是提供每个字段的元信息的一种机制,将 JSON 文档和结构类型里的字段一一映射起来。如果不存在标签,编码和解码过程会试图以大小写无关的方式,直接使用字段的名字进行匹配。如果无法匹配,对应的结构类型里的字段就包含其零值。

1
2
3
type A struct{
Name string `json:"name"`
}
  1. 由于 Go 语言支持复合语句调用,可以直接调用从 NewDecoder 函数返回的值的 Decode 方法,而不用把这个返回值存入变量。NewDecode 如下
1
func NewDecoder(r *io.Reader) *Decoder
  1. 我们 向 Decode 方法传入了指向 gResponse 类型的指针变量的地址,而这个地址的实际值为 nil。该方法调用后,这个指针变量会被赋给一个 gResponse 类型的值,并根据解码后的 JSON 文档做初始化。

  2. 使用 map 会很快

  3. 序列化(marshal)是指将数据转换为 JSON 字符串的过程

  4. MarshalIndent 很像 Marshal,只是用缩减对输出进行了格式化

  5. 如果不需要输出带有缩进格式的 JSON 字符串,json 包还提供了名为 Marshal 的函数来进行解码。这个函数产生的 JSON 字符串很适合作为在网络响应(如 Web API)的数据。函数 Marshal的工作原理和函数 MarshalIndent 一样,只不过没有用于前缀 prefix 和缩进 indent 的参数。

  6. 同样的理念扩展到了标准库的 io 包,而且提供的功能很神奇。这个包可以以流的方式高效处理数据,而不用考虑数据是什么,数据来自哪里,以及数据要发送到哪里的问题。与 stdout和 stdin 对应,这个包含有 io.Writer 和 io.Reader 两个接口。所有实现了这两个接口的类型的值,都可以使用 io 包提供的所有功能,也可以用于其他包里接受这两个接口的函数以及方法。这是用接口类型来构造函数和 API 最美妙的地方。开发人员可以基于这些现有功能进行组合,利用所有已经存在的实现,专注于解决业务问题。

1
2
3
type Writer interface {
Write(p []byte) (n int,err error)
}
  1. Read 最多读入 len(p)字节,保存到 p。这个方法返回读入的字节数(0 <= n
    (1) Rd l()0 <
    <l()Rd < l()
    习惯上 Read 会立刻返回可用的数据,而不等待更多的数据。
    (2) 当成功读取 n > 0 字节后,如果遇到错误或者文件读取完成,Read 方法会返回
    (2) > 0 Rd
    il
    0Rd
    下一次调用 Read 应该返回 0, EOF。
    (3) 调用者在返回的 n > 0 时,总应该先处理读入的数据,再处理错误 err。这样才
    (3) > 0
    (4) Read 的实现不鼓励返回 0 个读取字节的同时,返回 nil 值的错误。调用者需要将
    这种返回状态视为没有做任何操作,而不是遇到读取结束。
  2. 关于变量的声明:
1
2
3
4
5
var {
Stdin = NewFile(uintptr(syscall.Stdin), "/dev/stdin")
Stdout = NewFile(uintptr(syscall.Stdout), "/dev/stdout")
Stderr = NewFile(uintptr(syscall.Stderr), "/dev/stderr")
}
  1. 使用 MultiWriter,这样可以同时向文件和标准输出设备进行写操作
1
dest:=io.MultiWriter(os.Stdout, file)

IMG_0325.jpg


Go语言实战-7-8章
https://nrbackback.github.io/2021/03/23/Go语言实战-7-8章/
作者
John Doe
发布于
2021年3月23日
许可协议