GO语言核心30讲 实战与应用 (io包,bufio包,os包,网络服务,http,性能分析)

 原站地址:Go语言核心36讲_Golang_Go语言-极客时间

一、io包中的接口和工具

1. strings.Builder、strings.Reader 和 bytes.Buffer 这些类型实现了 io 包的很多接口,目的是什么

    是为了提高不同程序实体之间的互操作性。 程序实体是指比如网络和文件。

    比如 io.Copy:  func Copy(dst Writer, src Reader) (written int64, err error)

    里面的 Writer 和 Reader 可以是网络,也可以是文件,只要实现了io.Reader接口和io.Writer接口就都可以了。可以把不同的实体抽象成一个统一的实体。

2. 扩展接口和实现类型的区别是什么?

   实现类型是一个结构体; 扩展接口只是通过结构体的方式,嵌入了其他数据类型,达到扩展原有数据类型功能的目的,

   扩展接口是多个接口的集合。要实现扩展接口,需要实现多个接口。

   实现类型是把接口实现了,而扩展接口并没有把接口实现,它只是多个接口的集合。

3. 在io包中,io.Reader的扩展接口实现类型都有哪些? (提升对io包的了解程度)

    io.Reader的扩展接口有下面几种:

(1) io.ReadWriter:包含字节序列读取方法Read,和写入方法Write。

(2) io.ReadCloser:包含字节序列读取方法Read,和关闭方法Close。Close用于关闭数据读写的通路。这个接口是io.Reader和io.Closer的组合。

(3) io.ReadWriteCloser:io.Reader、io.Write 和io.Closer的组合。

(4) io.ReadSeeker:可以根据给定的偏移量去寻找新的位置,作为下一次读的起始索引。包含了寻找读写位置的基本方法Seek。io.Reader 和io.Seeker的组合。

(5) io.ReadWriteSeeker:io.Reader、io.Writer和io.Seeker的组合。

   io.Reader接口的实现类型有下面几种:

(1) *io.LimitedReader:方法Read返回的总数据量会受到限制,无论被调用多少次。

(2) *io.SectionReader:Read方法只能够读取原始数据中的某一个部分。与切片类似,只暴露在窗口之中的数据。

(3) *io.teeReader:接受io.Reader和io.Writer两个类型的参数,把Reader读到的数据,通过字节切片中转的方式,写入io.Writer处。通常使用在数据流的处理中,比如计算下载速度。

(4) *io.multiReader:接受多个io.Reader类型的参数,并从中顺序地读取数据。

(5) *io.pipe:同步内存管道的核心实现

(6) * io.PipeReader:同步内存管道的读取端

4. io包中的接口都有哪些?

(1) 核心接口:io.Reader、io.Writer和io.Closer

(2) io.ByteReader:读取一个单一的字节。 strings.Reader和bytes.Buffer 是它的实现类型。

     io.RuneReader:读取一个单一的Unicode 字符。strings.Reader和bytes.Buffer 是其实现类型

     io.ByteScanner:读取和读回退单个字节

     io.RuneScanner:读取和读回退单个Unicode 字符

     io.ReaderAt:只读取数据,不修改已读计数的值。

     io.ReaderFrom:从一个 Reader 中读取数据

     io.WriteTo:将数据写入到一个 Writer 中

     io.ReadWriter:读和写, *io.pipe 是其实现类型。

     io.ReadWriteCloser:读写和关闭管道,net包有它的实现类型。

     io.ByteWriter和io.WriterAt:功能和上面对应。实现类型是 *os.File

     io.Seeker:寻找并设定下一次读取或写入时的起始索引位置。strings.Reader和io.SectionReader都是其实现类型。

    io.Closer:关闭管道。io.PipeReader和io.PipeWriter 是其实现类型。

(3) io包中的简单接口共有 11 个。读取操作相关的5 个,写入操作相关的 4 个,关闭操作有关的1 个,读写位置设定相关的一个。此外,还包含了 9 个基于这些简单接口的扩展接口。

二、bufio包中的数据类型

1. bufio 包的程序实体,是在包装简单 I/O 接口类型值的基础上,添加了缓冲区

2. bufio包中的数据类型主要有:(1) Reader (2) Scanner (3) Writer 和 ReadWriter。

3. bufio.Reader类型值中的缓冲区起着怎样的作用?

        Reader值会预先从底层读取器里读出一部分数据,暂存于缓冲区之中。当Reader值下次读取数据时,先从缓冲区中读取。 减少了底层读取器的使用次数,降低读取的执行时间。 

        虽然,读取时会增加填充缓冲区的操作,但从总体上看,平均执行时间会有大幅度的缩短。

4. bufio.Reader类型通过私有的 fill方法 来整理缓冲区,把已读的空间腾出来,提供给底层读取器存放数据。

    整理方法就是把 [已读计数, 已写计数) 范围内的数据往最前面搬运,如下图所示:

5. bufio.Reader类型读取方法分别有哪些?

(1) Peek方法:读取缓冲区中的n个未读字节,n大于缓冲区长度的话,转而直接从底层读取器中读出数据。

      会从已读计数代表的索引位置开始读,读完不更改已读计数。 

(2) Read方法:其他和Peek方法一样,但会更改已读计数。 

(3) ReadSlice方法:持续地读取数据,直至遇到调用方给定的分隔符为止。

     如果缓冲区满了仍然找不到分隔符,会把整个缓冲区作为第一个结果值,缓冲区已满错误作为第二个结果值 返回。

(4) ReadBytes方法: 其他和ReadSlice方法一样,但缓冲区满了仍然找不到分隔符的话,会再次调用ReadSlice方法,整理缓冲区之后继续从底层读取器中读出数据,直至少找到分隔符或者读完全部数据。

6. 内容泄露:Peek方法、ReadSlice方法和ReadLine方法都有可能会造成内容泄露,因为他们都是返回直接基于缓冲区的字节切片。 只有Read方法不会内容泄露。

三、使用os包中的API

1. os代码包中的 API,是对操作系统的某方面功能的高层次抽象,使我们可以用统一的方式,操纵不同的操作系统。

    最有代表性的就是数据类型 os.File,它实现了io包3 个核心接口io.Reader、io.Writer和io.Closer,3 个简单接口,io.ReaderAt、io.Seeker和io.WriterAt,以及9 个扩展接口中的 7 个。

    所以除 文本文件、二进制文件、压缩文件、目录这些常见的形式之外,还有符号链接、各种物理设备、命名管道,以及套接字(socket)都可以被视为文件。

2. 怎样才能获得一个os.File类型的指针值(File值)? 

(1) os.Create函数:根据给定的路径创建一个新的文件。 

      会返回一个File值和一个错误值。可以通过File值进行读写,路径不存在的话会返回错误。

(2) os.Open函数:打开一个文件并返回包装了该文件的File值。

      只能从该File值中读取内容,而不能写入内容。

(3) os.NewFile函数:依据已经存在的文件描述符,新建包装了该文件的File值。

(4) os.OpenFile函数:新建或打开文件。

     可读可写。函数有 3 个参数,为name(文件路径)、flag(操作模式)和perm(权限模式)。

     可以视为这是基础函数,上面3个函数只是这个基础函数的参数组合。

3. 文件描述符,作为某个文件的一个标识存在。由 I/O 相关的系统调用返回,是很小的非负整数。

    任何文件的I/O 操作都需要这个文件描述符,它被存储在File值中。

4. File值的操作模式都有哪些?

    os.O_APPEND:追加模式写入内容。

    os.O_CREATE:路径不存在时创建文件。

    os.O_SYNC:在打开的文件上实施同步 I/O,保证读写的内容总会与硬盘上的数据同步。

    os.O_TRUNC:文件已存在时清空文件内容。

    多个操作模式可以通过按位或操作符 " | " 组合起来的。

5. File值的权限模式都有哪些?

    权限模式参数 是uint32类型的再定义类型,包含了 32 个比特位,每个比特位都有特定含义:

(1) 最高比特位,1 代表 目录。

(2) 第 26 个比特位,1代表 命名管道

(3) 最低的 9 个比特位才用于文件的权限,分别是 文件所有者、用户组、其他用户 对该文件的访问权限(读、写和执行)。

四、访问网络服务

1. 进程间通信,称为 IPC。主要方法包括:系统信号(signal)、管道(pipe)、套接字(socket)、文件锁、消息队列、信号量(semaphore)等。 socket 是最为通用和灵活的一种。

2. socket实例相关的API,是由一个名为 socket系统调用 代表的,它是连接应用程序和操作系统内核的桥梁。

    syscall代码包中,有一个与这个 socket系统调用 相对应的函数。函数本身是平台不相关的。在其底层,Go 为每个操作系统都做了适配,所以无论在哪个平台上总是有效的。

    Go 提供了 net包 来处理 socket。net包对socket进行了封装和抽象,无论建立什么协议,都只需要调用 net.Dial函数 即可。 

    net包中的很多程序实体,都会直接或间接地使用到syscall.Socket函数,比如net.Dial函数

3. net.Dial函数的第一个参数network有哪些可选值?

(1) TCP、TCP4、TCP6:代表 TCP 协议,自适应、第四版、第六版

(2) UDP、UDP4、UDP6:代表 UDP 协议,自适应、第四版、第六版

(3) unix、unixgram、unixpacket:代表 Unix 通信域下的内部 socket 协议,socket 类型分别为SOCK_STREAM、SOCK_DGRAM、SOCK_SEQPACKET。

4. 有消息边界,是指内核程序在发送或接收数据时,是以消息为单位的。

    有逻辑连接,是指通信双方在收发数据之前必须先建立网络连接

5. TCP 没有消息边界,有逻辑连接。 好处:保证可靠性,有序,双向传输。坏处:速度慢。

    UDP 有消息边界,没有逻辑连接。好处:速度快。坏处:不保证可靠,无需,只能单向传输。

6. net.DialTimeout函数 调用时给定的超时时间意味着什么?

   给定的超时时间,意味着函数为网络建立连接,可以等待的最长时间。

   调用net.DialTimeout函数之后,时间主要花费在 “解析参数network和address的值”,以及“创建 socket 实例并建立网络连接”这两件事情上。

五、基于HTTP协议的网络服务

1. net/http代码包 可以使用 基于http协议的网络服务。比如 http.Get函数。

    实例:  resp, err := http.Get(url)    返回两个值:resp 和 err

    resp:数据类型是*http.Response,响应内容的结构体。

    err:数据类型是error,创建、发送请求、接收和解析、响应 全过程中,可能发生的错误。

2. http.Get函数

    http.Get函数会在内部使用 缺省HTTP客户端,它是 net/http包中的公开变量DefaultClient代表的,其类型是 *http.Client, 它是开箱即用的。

3. http.Client类型中的 Transport字段 代表着什么?

(1) Transport字段 的数据类型是 *http.Transport,会在内部使用net.Dialer类型值,实现 连接 和 超时 的功能。

(2) http.Transport类型的值(以下简称Transport值)会对每一个网络服务的空闲连接的总数做出限定。 一个网络服务由网络地址、网络协议、代理 三方面来鉴定。

     在默认情况下,空闲连接总数最大为100,而每个网络服务的最大空闲连接数为2。

(3) *http.Transport 是 http.RoundTripper接口 的实现类型。RoundTripper 内部有一个DefaultTransport的缺省值。 前面提到的 缺省HTTP客户端 DefaultClient ,其实也就是使用这个DefaultTransport。

(4) 总的来说 Transport字段代表着:向网络服务发送 HTTP 请求,并从网络服务接收 HTTP 响应的整个操作过程。

4. http.Server类型的ListenAndServe方法都做了哪些事情?

(1) 对一个基于 TCP 协议的网络地址进行监听(net.Listen),并对接收到的 HTTP 请求进行处理。

(2) 默认开启针对网络连接的存活探测机制,以保证连接是持久的。

(3) 该方法会一直执行,直到有严重的错误发生或者被外界关掉。

5. net.Listen函数都做了哪些事情?

(1) 解析参数值中网络地址包含的 IP 地址端口号

(2) 根据给定的网络协议,确定监听的方法,并开始进行监听。

6. http.Server类型的Serve方法是怎样接受和处理 HTTP 请求的?

(1) listen之后,进入一个for循环。

(2) for循环中,Accept方法会被不断地调用,该方法返回两个值:

     net.Conn类型:代表包含了新到来的 HTTP 请求的网络连接

     error类型:代表是否发生了错误。暂时性的错误的话for会继续执行,否则会for循环都会被终止

(3) 把 net.Conn类型的结果值包装成一个*http.conn类型,并启用新的 goroutine ,去调用这个conn值的serve方法,来对当前的 HTTP 请求进行处理。

六、程序性能分析基础

1. 性能分析 API 在这三个代码包中:

(1) runtime/pprof

(2) net/http/pprof

(3) runtime/trace

2. 概要文件(Profile)

   runtime 代码包中包含更底层的 API,用来收集程序运行过程中的一些关键指标,并生成概要文件,提供分析使用。

    go test 命令也可以在测试完成后生成 概要文件(Profile)。

3. 概要文件采样时刻的内容

      分析程序性能的概要文件有CPU、内存、阻塞三种概要文件。文件里每段概要信息都记录着,某个采样时刻的内容分别是:

(1) CPU 概要文件:CPU 上正在执行的 Go 代码。

(2) 内存概要文件:内存的使用情况,已分配和已释放的字节数量和对象数量。

(3) 阻塞概要文件:goroutine 阻塞事件

4. 概要文件内容格式

    这些概要文件是以二进制存储的,是通过 protocol buffers 生成的二进制字节流。

    可以使用 go tool pprof 工具查看。

5. 怎样让程序对 CPU 概要信息进行采样?

(1) 进行采样调用StartCPUProfile函数;停止采样调用StopCPUProfile函数。

(2) StartCPUProfile函数,会设定 CPU 概要信息的采样频率,并在单独的 goroutine 中进行收集和输出。 CPU采样频率总是固定100赫兹的,经过大量实验证明最优。

(3) StopCPUProfile函数,会采样频率设为0,采样工作停止。

6. 怎样设定内存概要信息的采样频率?

(1) 为 runtime.MemProfileRate变量赋值即可。 含义是,每分配多少个字节,就对堆内存的使用情况进行一次采样。缺省值是512 KB。

(2) 越早设定越好,避免运行时造成不良影响。最好只在main函数的开始处设定一次。

(3) 想获取内存概要信息的时候, 调用WriteHeapProfile函数。但它并非实时数据,是在最近一次的内存垃圾收集工作完成时产生的。

(4) 可以调用runtime.ReadMemStats函数,获得实时数据。不过该函数会引起 Go 语言调度器的短暂停顿。

7. 怎样获取到阻塞概要信息?

(1) 调用runtime包中的SetBlockProfileRate函数,可对阻塞概要信息的采样频率进行设定。

(2) 函数有一个名叫rate的参数,int类型。含义是,只要发现一个阻塞事件的持续时间达到了多少纳秒,就对其进行采样。

(3) 在runtime包中,有一个名叫blockprofilerate的私有变量,uint64类型。含义是,只要发现一个阻塞事件的持续时间跨越了多少个 CPU 时钟周期,就对其进行采样。和(2)的区别仅仅在于单位不同。 

(4) 缺省值是0,所以默认情况下并不会记录任何阻塞事件。

(5) 需要获取阻塞概要信息的时候,做两步操作:

    调用runtime/pprof包中的 Lookup函数 并传入参数值"block",得到*runtime/pprof.Profile类型的值     

    调用Profile值的 WriteTo方法,把概要信息写进指定的写入器中。

8. runtime/pprof.Lookup函数的正确调用方式是什么?

(1) Lookup函数的功能是,提供 给定的名称 相对应的概要信息。

(2) 给定的名称 包括:

goroutine:收集当前正在使用的所有 goroutine 堆栈跟踪信息。会引起 Go 调度器的短暂停顿。

heap:收集与堆内存的分配和释放有关的采样信息。也就是前面的内存概要信息。

allocs:与heap大致一样,但 allocs 收集到的是已分配空间(已分配不管有否释放),heap是在用空间(已分配未释放)。

threadcreate:收集堆栈跟踪信息,描绘出代码调用链。

block:在代码同步竞争中被阻塞的代码的堆栈跟踪信息。就是前面的阻塞概要信息。

mutex:在代码同步竞争中,获得过执行的代码的堆栈跟踪信息。

9. 如何为基于 HTTP 协议的网络服务添加性能分析接口?

(1) 在程序中导入net/http/pprof代码包。

     import _ "net/http/pprof"

(2) 启动网络服务并开始监听。

     log.Println(http.ListenAndServe("localhost:8082", nil))

(3) 在浏览器中访问  http://localhost:8082/debug/pprof  看到一个简约的网页

(4) 在/debug/pprof/ 这个URL路径下还有很多可用的子路径,包括:profile,trace,allocs、block、goroutine、heap、mutex、threadcreate。

(5) 返回二进制内容时,需要使用  go tool pprof 工具去查看。

    go tool pprof http://localhost:6060/debug/pprof/profile?seconds=60

  • 3
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值