《Go程序设计语言》走进云原生

选书

2019年接触过一段go,一些docker、k18s等容器组件也都用go实现开发,云服务发展普及后,虚拟化、微服务、数据库、消息等中间件越来越多的用go语言实现,可以说已经形成一个比较明显的趋势。

从行业的岗位数量来看,go语言的岗位也是越来越多的,从BAT、TMD等公司的核心服务看,之后重构的方向是两个,商业化相关的还是Java为主,中台、中间件、核心服务越来越多的迁移到go语言上。

从后续个人职业发展的技术栈和市场看,go语言的掌握是必要和有益的。

这本书的厚度适中、介绍了go比较核心的一些语言特性,协程、通道、锁、接口、反射、unsafe和cgo等;书评还不错,翻译中有比较明显的错别字,例如把崩溃翻译成了奔溃,整体来说还算易读。

读书

hello world

package main

import (

"fmt"

"os"

)

func main(){

fmt.Println("Hello, 世界")

}

hello的特点:

  • 经常用到打印后换行的功能需要拼接PHP_EOL,go的思路是将常用场景的功能封装到系统函数里,可以说语言的设计思路是将常用的场景封装为函数。
  • 函数的逻辑行不需要分号结束,这点和c不一样,更像python的风格。
  • 系统包的使用是按需导入,这点和python一样,减少包大小,提升后续链接、编译、维护等环节效率。

程序结构

计算机程序的核心目的是计算数据,故所有的程序结构都是数据的声明、计算逻辑的封装、外加逻辑的调度三个部分。

名称命名规则:以字母或下划线开头,后面可以跟任意数量的字符、数字或下划线。

声明分为四个类型:变量、常量、类型和函数。

文件结构:

  • 每一个文件都以package声明开头,表名文件属于哪个包。
  • import声明,导入依赖的包。
  • 包级别的类型、变量、常量、函数的声明,不区分顺序。

变量:

  • ​ var name type = expression
  • 类型和表达式可以省略一个,不能都省略,大小写敏感
  • 短变量声明由表达式的类型决定,name := expression
  • 指针是一个变量的地址
  • 使用new函数创建某类型数据,p := new(int)
  • 变量的生命周期分为函数、包、程序三个级别的
  • 支持简单赋值 x = 1, 支持多重赋值 x, y = y, x
  • 隐式赋值,例如形参赋值,return赋值等
  • 类型声明,type color int

包和文件

  • go中的包和其他语言的库或模块类似
  • 每个包都是独立的命名空间
  • 若包的函数首字母为大写则可导出(其他包可用)
  • go通过导入路径来标识包
  • 导入的包可以用短名字绑定,简化使用
  • 作用域是声明在程序文本中出现的区域,是个编译时属性
  • 生命周期是是变量在程序执行期间能被程序的其他部分所引用的起止时间,是运行时属性

基础数据类型

整形

  • int8 int16 int32 int64 有符号
  • uint8 uint16 uint32 uint64 无符号
  • rune 是int32类型的同义词,为什么有这个同义词?常常指Unicode码点
  • uintptr大小不明确,主要存放指针,用于底层编程
  • 超出表示范围会溢出,高位无提示丢弃,故计算时需要校验是否溢出

浮点数

  • 浮点数转整形会舍弃小数部分、趋零截尾(整数向下取整,负数向上取整)
  • float32 float64 两种浮点类型

复数

  • complex64 complex128两种复数,分别由float32和float64构成

字符串

  • 是一个不可变字节序列,可包含任意数据,包括0值字节
  • len返回字节数
  • s[i:j]产生一个新串,左闭右开
  • 源文件总是用utf-8编码,故字符串会按utf-8解读
  • utf-8以字节为单位对Unicode码点做变长编码
  • bytes strings strconv unicode四个常用字符串操作函数
  • bytes 用于操作字节
  • strings 搜索 替换 比较 切分 连接等
  • strconv 转换布尔值 整数 浮点数与之对应的字符串形式
  • unicode判别符号特性,如:isDigit IsLetter IsLower

常量

  • const name = value
  • const (a = value b = value)
  • 常量生成器iota

复合数据类型

数组

  • 具有固定长度的,具有零个或多个相同数据类型元素的序列
  • var a [3]int
  • fmt.Println(a[0])
  • slice是一个拥有相同类型元素的变长序列,var runes []rune
  • append追加元素到slice后面

map

  • 是一个拥有键值对元素的无序集合
  • ages := make(map[string]int)

结构体

  • 是将零个或者多个任意类型的命名变量组合在一起的聚合数据类型
  • type Employee struct {Id int Name string …}
  • 通过字面量赋值 p := Point{1,2}

json

  • json是一种发送和接收格式化信息的标准
  • json.Marshal(datalist)生成json字符串
  • json.Unmarshal(data, $title)解析支持数据裁剪

文本和html

  • 类似于smarty的模板渲染能力
  • 可用于格式化输出

函数

  • 包含连续的执行语句,可在代码中通过函数名来执行这组语句
  • func funcname(parameter-list)(result-list){body}
  • 和php不同,支持多返回值
  • 使用err返回错误信息,使用nil标识有无错误
  • 文件结束标识io.EOF
  • 支持匿名函数,例如用在map计算
  • 变长函数被调用的时候可以有可变的参数个数,用…来声明
  • 引用空指针、访问越界,在运行时检测到就会宕机
  • 可以用recover()进行恢复

方法

  • 方法声明和函数不同点在于绑定到某个数据上
  • func (p Point) Distance(q Point)float64{}
  • p成为方法的接收者
  • p.Distance(q)
  • nil是一个合法的方法接收者

封装

  • go中封装的单元是包而不是类型
  • 结构体数据对包内的所有代码都是可见的
  • 首字母大写的标识是可以从包中导出的,而首字母没有大写的则不导出
  • 不可导出的有点:避免意外的修改导致函数处理问题,改变api可以不考虑包外使用兼容性,避免修改对象内的变量

接口

  • 接口是对其他类型行为的概括与抽象
  • go语言接口是隐式实现
  • 接口既约定
  • 可以把一种类型替换为满足同一接口的另一种类型的特性称为可取代性
  • 接口支持组合接口形成新接口
  • 调用nil接口的任何方法都会导致崩溃
  • error是一个接口类型,包含一个返回错误消息的方法
  • 类型断言是一个作用在接口值上的操作
  • 子类型多态和特设多态

goroutine和通道

  • 每一个并发执行的活动成为goroutine
  • 通信顺序进程,CSP是一种并发的模式
  • 通道是并发执行体之间的连接
  • 通道主要有发送和接收两个操作,统称为通信
  • 无缓冲通道和缓冲通道(类似队列)
  • 一个的输出是另一个的输入,称之为管道(通道和管道的区别)
  • 约束发送或者接收的单向通道
  • select可以作用在多个通道上,实现多路复用

共享变量

  • 若一个函数在并发调用时仍然能正确工作,那么这个函数是并发安全的(并发不安全的原因是非顺序执行)
  • 并发问题,死锁、活锁、资源耗尽
  • 互斥锁:sync.Mutex(导致顺序执行,影响效率)
  • 读写互斥锁:sync.RWMutext,解决读并发效率问题
  • 延迟初始化:sync.Once
  • 竞态检查器:-race 参数
  • 并发非阻塞缓存:函数记忆(memoizing)
goroutine与线程区别
  • goroutine是动态栈分配,最小2kb,最大为1G,而线程为2M。
  • goroutine将线程的系统调度,改为自己实现调度器,减少内核态切换开销
  • GOMAXPROCS最大线程数,设置为1则变为单线程执行

包和工具

  • import导入包
  • package声明包
  • 空导入 import _ “image/png”,避免导入未使用编译错误

go工具

  • 工作空间组织:src bin pkg
  • 包下载:go get
  • 包构建:go build
  • 包文档:go doc time
  • 包检查:go list -json hash

测试

  • go test跑*_test.go 单测函数
  • go test -bench=. 性能测试
  • go test -cpuprofile=cpu.out cpu性能分析-cpu
  • go test -blockprofile=block.out 性能分析-io
  • go test -memprofile=mem.out 性能分析-内存

反射

  • 可更新变量在运行时查看值、类型和调用方法及对其操作的机制成为反射
  • reflect.Value reflect.Type

低级编程

  • 查看内存使用及分布:unsafe.Sizeof Alignof Offsetof
  • 深度相等:reflect.DeepEqual
  • cgo调用c代码
  • FFI调用c代码
  • SWIG调用c++代码

用书

目前业务
  • 业务性能优化,部分顺序执行的逻辑可以并行处理,提升接口性能。
  • 消息通知,可以收敛接口和消息队列,异步处理通知,减少消息中间件。
  • 运维部署,减少多文件的维护和软链创建,可以提升部署效率。
技术学习
  • 更好的了解云原生的技术实现和发展。
职业发展
  • 可以熟悉、掌握更多的开发、运维的系统及中间件。
  • 在岗位的选择上有更多的选择机会。
您的支持是我最大的动力!