登录
登录 注册新账号
注册
已有账号登录
深入Go底层原理,重写Redis中间件实战【高清无密】
原动力 阅读 70 次
6月17日发布

Download: 深入Go底层原理,重写Redis中间件实战

Go快速入门

1 Go 安装
最新版本下载地址官方下载 golang.org,当前是 1.13.6。如无法访问,可以在 studygolang.com/dl 下载

使用 Linux,可以用如下方式快速安装。

$ wget https://studygolang.com/dl/golang/go1.13.6.linux-amd64.tar.gz
$ tar -zxvf go1.13.6.linux-amd64.tar.gz
$ sudo mv go /usr/local/
$ go version

go version go1.13.6 linux/amd64
从 Go 1.11 版本开始,Go 提供了 Go Modules 的机制,推荐设置以下环境变量,第三方包的下载将通过国内镜像,避免出现官方网址被屏蔽的问题。

$ go env -w GOPROXY=https://goproxy.cn,direct
或在 ~/.profile 中设置环境变量

export GOPROXY=https://goproxy.cn

2 Hello World
新建一个文件 main.go,写入

package main

import "fmt"

func main() {
    fmt.Println("Hello World!")
}

执行go run main.go 或 go run .,将会输出

$ go run .

Hello World!

3 变量与内置数据类型
3.1 变量(Variable)
Go 语言是静态类型的,变量声明时必须明确变量的类型。Go 语言与其他语言显著不同的一个地方在于,Go 语言的类型在变量后面。比如 java 中,声明一个整体一般写成 int a = 1,在 Go 语言中,需要这么写:

var a int // 如果没有赋值,默认为0
var a int = 1 // 声明时赋值
var a = 1 // 声明时赋值
var a = 1,因为 1  int 类型的,所以赋值时,a 自动被确定为 int 类型,所以类型名可以省略不写,这种方式还有一种更简单的表达:

a := 1
msg := "Hello World!"

3.2 简单类型
空值:nil

整型类型: int(取决于操作系统), int8, int16, int32, int64, uint8, uint16, …

浮点数类型:float32, float64

字节类型:byte (等价于uint8)

字符串类型:string

布尔值类型:boolean,(true 或 false)

深入Go底层原理,重写Redis中间件实战- Go结构体的内存布局

结构体大小
结构体是占用一块连续的内存,一个结构体变量的大小是由结构体中的字段决定。

type Foo struct {
    A int8 // 1
    B int8 // 1
    C int8 // 1
}

var f Foo
fmt.Println(unsafe.Sizeof(f))  // 3

内存对齐
但是结构体的大小又不完全由结构体的字段决定,例如:

type Bar struct {
    x int32 // 4
    y *Foo  // 8
    z bool  // 1
}

var b1 Bar
fmt.Println(unsafe.Sizeof(b1)) // 24
有的同学可能会认为结构体变量b1的内存布局如下图所示,那么问题来了,结构体变量b1的大小怎么会是24呢?

memory layout of Bar1

很显然结构体变量b1的内存布局和上图中的并不一致,实际上的布局应该如下图所示,灰色虚线的部分就是内存对齐时的填充(padding)部分。

memory layout of Bar1

Go 在编译的时候会按照一定的规则自动进行内存对齐。之所以这么设计是为了减少 CPU 访问内存的次数,加大 CPU 访问内存的吞吐量。如果不进行内存对齐的话,很可能就会增加CPU访问内存的次数。例如下图中CPU想要获取b1.y字段的值可能就需要两次总线周期。

word size

因为 CPU 访问内存时,并不是逐个字节访问,而是以字(word)为单位访问。比如 64位CPU的字长(word size)为8bytes,那么CPU访问内存的单位也是8字节,每次加载的内存数据也是固定的若干字长,如8words(64bytes)、16words(128bytes)等。

对齐保证
我们上面已经知道了可以通过内置unsafe包的Sizeof函数来获取一个变量的大小,此外我们还可以通过内置unsafe包的Alignof函数来获取一个变量的对齐系数,例如:

// 结构体变量b1的对齐系数
fmt.Println(unsafe.Alignof(b1))   // 8
// b1每一个字段的对齐系数
fmt.Println(unsafe.Alignof(b1.x)) // 4:表示此字段须按4的倍数对齐
fmt.Println(unsafe.Alignof(b1.y)) // 8:表示此字段须按8的倍数对齐
fmt.Println(unsafe.Alignof(b1.z)) // 1:表示此字段须按1的倍数对齐
unsafe.Alignof()的规则如下:

对于任意类型的变量 x ,unsafe.Alignof(x) 至少为 1。
对于 struct 类型的变量 x,计算 x 每一个字段 f 的 unsafe.Alignof(x.f),unsafe.Alignof(x) 等于其中的最大值。
对于 array 类型的变量 x,unsafe.Alignof(x) 等于构成数组的元素类型的对齐倍数。
在了解了上面的规则之后,我们就可以通过调整结构体 Bar 中字段的顺序来减少其大小:

type Bar2 struct {
    x int32 // 4
    z bool  // 1
    y *Foo  // 8
}

var b2 Bar2
fmt.Println(unsafe.Sizeof(b2)) // 16
此时结构体 Bar2 变量的内存布局示意图如下:

memory layout of Bar2

或者将字段顺序调整为以下顺序。

type Bar3 struct {
    z bool  // 1
    x int32 // 4
    y *Foo  // 8
}

var b3 Bar3
fmt.Println(unsafe.Sizeof(b3)) // 16