阅读目录

  • ​​1 特性篇​​
  • ​​什么是协程(Goroutine)​​
  • ​​进程、线程、协程​​
  • ​​Golang 使用什么数据类型?​​
  • ​​字符串的小问题​​
  • ​​数组定义问题​​
  • ​​对 rune 字面量的理解和数组的语法​​
  • ​​内存四区​​
  • ​​Go 支持什么形式的类型转换?​​
  • ​​空结构体的作用​​
  • ​​单引号,双引号,反引号的区别?​​
  • ​​* 如何停止一个 Goroutine?​​
  • ​​Go 语言中 cap 函数可以作用于哪些内容?​​
  • ​​Printf(),Sprintf(),FprintF() 都是格式化输出,有什么不同?​​
  • ​​*golang 中 make 和 new 的区别?​​
  • ​​for-range 切片的时候,它的地址会发生变化么?​​
  • ​​context 使用场景和用途?​​
  • ​​常量计数器 iota​​
  • ​​defer 特性相关​​
  • ​​defer 遇见 panic​​
  • ​​介绍下 rune 类型​​
  • ​​介绍一下 interface​​
  • ​​接口的类型检查① 断言​​
  • ​​go 语言如何实现面对对象编程​​
  • ​​go的结构体能不能比较?​​
  • ​​waitGroup对象,可以实现同一时间启动n个协程​​
  • ​​切片与数组的区别​​
  • ​​切片的创建​​
  • ​​直接声明​​
  • ​​为什么map的遍历是无需的?​​
  • ​​map 的删除​​
  • ​​nil map 和空 map 有何不同?​​
  • ​​map 中删除一个 key,它的内存会释放么?​​
  • ​​Student 结构值运行下面程序发生什么?​​
  • ​​2 channel 篇​​
  • ​​并行与并发、进程与线程与协程​​
  • ​​Go 缓冲通道与无缓冲通道的区别​​
  • ​​GMP 调度模型篇​​
  • ​​调度器有哪些设计策略?​​
  • ​​3 内存逃逸篇​​
  • ​​什么是内存逃逸,为什么需要内存逃逸?​​
  • ​​如何打印逃逸分析信息​​
  • ​​Golang GC、三色标记、混合写屏障机制​​
  • ​​背景知识​​
  • ​​GC 相关术语​​
  • ​​Go 的 GC 发展演变史​​
  • ​​v 1.3-标记清除法​​
  • ​​v1.5 三色标记法​​
  • ​​1、初始时,所有对象被标记为白色​​
  • ​​2、GC开始,遍历rootset,将直接可达的对象标记为灰色​​
  • ​​3、遍历灰色对象,将直接可达对象标记为灰色,并将自身标记为黑色​​
  • ​​4、重复第3步,直到标记完所有的对象​​
  • ​​5、将标记为白色的对象当做垃圾回收掉​​
  • ​​总结​​
  • ​​Redis​​
  • ​​Redis hash 冲突​​

1 特性篇

什么是协程(Goroutine)

协程是用户态轻量级线程,是线程调度的基本单位。

通常在函数前加上go关键字就能实现并发。

一个Goroutine会以一个很小的栈启动2KB或4KB,当遇到栈空间不足时,栈会自动伸缩, 因此可以轻易实现成千上万个goroutine同时启动。

进程、线程、协程

进程是一个程序的数据集合。

线程是进程的一个最小单位。

协程是用户控制的轻量级线程,它是一种特殊的线程,可以在单个线程中实现多任务的并发处理。

goroutine 是轻量级的线程,占用资源很少,但如果一直得不到释放并且还在不断创建新协程,毫无疑问是有问题的,并且是要在程序运行几天,甚至更长的时间才能发现的问题。

Golang 使用什么数据类型?

  • 布尔型
  • 数值型(整型、浮点型)
  • 字符串
  • 指针
  • 数组
  • 结构体
  • 切片
  • map
  • chan
  • 接口
  • 函数

字符串的小问题

​==​​nil​

数组定义问题

数组是可以以指定下标的方式定义的,例如:

对 rune 字面量的理解和数组的语法

原因:

以下标的方式定义数组内的元素,'c’的ascll为99,故长度为100。

内存四区

  • 代码区:存放代码
  • 全局区:常量+全局变量。
    最终在进程退出时,由操作系统回收。
  • 堆区:空间充裕,数据存放时间较久。
    一般由开发者分配,启动 Golang 的 GC 由 GC 清除机制自动回收。
  • 栈区:空间较小,要求数据读写性能高,数据存放时间较短暂。由编译器自动分配和释放,存放函数的参数值、局部变量、返回值等、局部变量等(局部变量如果产生逃逸现象,可能会挂在在堆区)

Go 支持什么形式的类型转换?

Go支持显示类型的转换,以满足严格的类型要求。

空结构体的作用

​struct{}​

定义:

特性:

​zerobase​

使用场景:

​1​
​2​
​3​

单引号,双引号,反引号的区别?

  • 单引号,表示 byte 或者 rune 类型,对应 uint8 和 int32 类型;默认直接赋值的话是 rune 类型。
  • 双引号,字符串类型,不允许修改。实际上是字符数组,可以用下标索引其中的某个字节。
  • 反引号,表示字符串字面量,反引号中的字符不支持任何转义,写什么就是什么。

* 如何停止一个 Goroutine?

  • ① for - select 方法,采用通道,通知协程退出。
  • ②采用 context 包。

Go 语言中 cap 函数可以作用于哪些内容?

  • 数组
  • 切片
  • 通道

Printf(),Sprintf(),FprintF() 都是格式化输出,有什么不同?

  • Printf():是标准输出,一般用于打印。
  • Sprintf():把格式化字符串输出到字符串,并返回。
  • FprintF():把格式化字符串输出到实现了 io.witer方法的类型,比如文件,写入文件。

*golang 中 make 和 new 的区别?

共同点:都会分配内存空间(堆上)

不同点:

  • ① 作用变量不同,new 可以为任意类型分配内存;但是 make 只能给,切片、map、chan分配内存。
  • ② 返回类型不同,new 返回的是指向变量的指针;make 返回的是上边三种变量类型本身。
  • ③ new 对分配的内存清零;make会根据你的设定进行初始化,比如在设置长度、容量的时候。

for-range 切片的时候,它的地址会发生变化么?

​for a,b := range slice​

对于切片遍历的话,b 是复制的切片中的元素,改变 b,并不会影响切片中的元素。

context 使用场景和用途?

context 的主要作用:

协调多个 groutine 中的代码执行 “取消” 操作,并且可以存储键值对。最重要的是它是并发安全的。

① 可以存储键值对,供上下文(协程间)读取【建议不要使用】

② 优雅的主动取消协程(Cancel)。主动取消子协程运行,用不到子协程了,回收资源。比如一个http请求,客户端突然断开了,就直接cancel,停止后续的操作;

③ 超时退出协程(Timeout),比如如果三秒之内没有执行结束,直接退出该协程;

④ 截止时间退出协程(Deadline),如果一个业务,2点到4点为业务活动期,4点截止结束任务(协程)

常量计数器 iota

iota 常量计数器,具有自增的特点,可以简化有关于数字增长的常量的定义。

特点:

① iota只能出现在const代码块中。
② 不同 const 代码块中的 iota 互不影响。

​_​

④ 没有表达式的常量定义复用上一行的表达式。

defer 特性相关

​1​
​defer为延迟函数​
​2​

每个defer对应一个实例,多个defer,也就是多个实例,使用指针连接成一个单链表,每次写一个defer实例,就插入到这个单链表的头部,函数结束的时候,从头部依次取出,并执行defer。可以类比“栈”的先进后出方式。

​3​
​4​

return虽先执行,但是defer中有改变具名返回值的操作,导致返回值发生了改变(至于为什么,只能说Go就是这样定义的)

defer 遇见 panic

​或函数体到末尾​

① defer遇见panic,但是并不捕获异常的情况。

和 return 一样,只不过 panic 前面的 defer 执行完之后,跳出函数,直接报异常。

② defer遇见panic,并捕获异常。

和上述不同的是,当运行的defer中捕获异常,并恢复之后,跳出函数,不会报异常,会继续执行。

但是需要注意的是,在发生恐慌的函数内,panic之后的程序都不会被执行。

输出:
这类题目记住,return 返回值的时候,是赋值操作,并没指针。

介绍下 rune 类型

rune 是 int32 的别名,等同于int32,常用来处理 unicode 或 utf-8 字符,用来区分字符值和整数值。

这里和 byte 进行对比,byte是uint8,常用来处理 ascii 字符。

那么有什么不同呢?

举个例子

介绍一下 interface

interface 特性,

  • interface 是 method 方法的集合。
  • interface是一种类型,并且是指针类型
  • 实现统一的接口(成为接口类型的数据)
  • 利用统一的接口各干各的事(方法的不同实现方式)
  • interface的更重要的作用在于多态实现

interface 使用

  • 接口的使用不仅仅针对结构体,自定义类型、变量等等都可以实现接口。
  • 如果一个接口没有任何方法,我们称为空接口,由于空接口没有方法,所以任何类型都实现了空接口。
  • 要实现一个接口,必须实现该接口里面的所有方法。

接口的类型检查① 断言

② 如果接口类型可能有多种情况的话,采用 Type Switch 方法。

go 语言如何实现面对对象编程

面对对象编程的三个基本特征:

  • 封装
  • 继承
  • 多态

go 通过结构体实现:

  • 封装
  • 继承
  • 通过接口实现多态

go的结构体能不能比较?

结构体中含有不能比较的类型时,不能比较;

声明两个比较值的结构体的名字不同,即使字段名、类型、顺序相同,也不能比较(强转类型可以比较),说白了,必须用同一个结构体类型声明的值,才能比较;

waitGroup对象,可以实现同一时间启动n个协程

一个waitGroup对象,可以实现同一时间启动n个协程,并发执行,等n个协程全部执行结束后,在继续往下执行的一个功能。

通过 Add() 方法设置启动了多少个协程,在每一个协程结束的时候调用 Done() 方法,计数减一,同时使用 wait() 方法阻塞主协程,等待全部的协程执行结束。

切片与数组的区别

共同点:

① 都是存储一系列相同类型的数据结构。
② 都可以通过下标来访问。
③ 都有 len 和 cap 这种概念。

不同点:

① 数组是定长的,且大小不能更改,是值类型。比如在函数参数传入的时候,形参和实参类型必须一模一样的。

② 切片是不定长的,容量是可以自动扩容的。切片传入函数的时候是值类型。

实际上,切片的传参是使用值传递。

函数能够对切片进行修改,是因为在函数中,拷贝切片所指的数组发生了变化,因此原切片的结果也发生变化。

答:一个方法就是用指针。

切片的创建

序号

方式

示例

1

直接声明

var slice []int

2

new

slice := *new([]int)

3

字面量

slice := []int{1,2,3,4,5}

4

make

slice := make([]int,10)

5

从切片或数组截取

slice := array[:5] 或 slice := souceSlice[2:4]

直接声明

这里重点说一下 nil 切片和空切片。

nil 切片

空切片

这两种方式的 len 和 cap 均为 0。

但是不同的是:

  • nil 切片和 nil 的比较结果是 true。
  • 空切片和 nil 的比较结果是 false,且同一程序里面,任何类型的空切片的底层数组指针的都指向同一地址。

为什么map的遍历是无需的?

① 遍历的起始位置每次都是随机的。
② 由于扩容,会导致 key 所处的桶发生变化。

map 的删除

key,value 清零。
对应位置的 tophash 置为 Empty。

1、所有Go版本通用方法

nil map 和空 map 有何不同?

​var m map[string]int​

空 map 表示 map 已经被初始化,只是长度为 0,还并未赋于键值对。

​nil map:m[“a”]​​m[“a”] = 1​​nil map​

map 中删除一个 key,它的内存会释放么?

① 如果删除的键值对都是值类型(int,float,bool,string以及数组和struct),map的内存不会自动释放。

② 如果删除的键值对中有(指针,slice,map,chan等),且该引用未被程序的其他位置使用,则该引用的内存会被释放,但是map中为存放这个类型而申请的内存不会被释放。

上述两种情况,map为存储键值所申请的空间,均不会被立即释放。等待GC到来,才会被释放。

③ 将map设置为nil后,内存被回收。

Student 结构值运行下面程序发生什么?

编译失败:

​map[string]Student​​Student​​list[“student”] = student​
​list[“student”]​

那么值引用的特点是只读。

​list[“student”].Name = "LDB"​

2 channel 篇

并行与并发、进程与线程与协程

并行指物理上同时执行,并发指能够让多个任务在逻辑上交织执行的程序设计。

Go 缓冲通道与无缓冲通道的区别

​1​
​2​
​3​

​1​
​2​
​3​

GMP 调度模型篇

  • G代表协程;
  • P代表协程处理器;
  • M代表内核级线程。

什么是GMP模型?

当我们写一个并发程序,操作系统会对其进行调度,线程是操作系统调度的最小单位,而不是协程,所以GMP模型就是想办法将用户创建的众多协程分配到线程上的这么一个过程。

调度器有哪些设计策略?

复用线程:避免重复的创建、销毁线程,而是对线程的复用(work stealing机制和hand off机制)

抢占机制:一个协程占用cpu的时长是有时间限制的,当该协程运行超时之后,会被其他协程抢占,防止其他协程被饿死,

3 内存逃逸篇

什么是内存逃逸,为什么需要内存逃逸?

定义:
一个在栈区存储的变量,
因为被堆区的变量引用,
使得该变量会从栈区逃逸到堆区;

原因:

go 语言并不需要程序员像使用c/c++那样,需要自己去释放内存,go做了自动化处理。

所以go申请的局部变量(无论是 var 还是 new 申请的),只要没有超过一定大小,都被先分配到栈上,但是如果该变量被堆上的变量引用了得话,该变量必须逃逸到堆上,防止栈区的内存会被系统全部自动释放掉,从而导致被引用的变量丢失,产生野指针。

逃逸的堆区的变量,在需要被回收的时候,会被 GC 进行回收。

如何打印逃逸分析信息

逃逸分析在编译阶段进行,由编译器完成。

​-m​​-l​

Golang GC、三色标记、混合写屏障机制

没有躺赢的命,那就站起来跑!

讲讲垃圾回收机制

圾回收机制,是一种自动内存管理机制。

在程序中定义一个变量后,会在内存中开辟相应空间进行存储。当不需要此变量后,需要手动销毁此对象,并释放内存。

对这种不再使用的内存资源进行自动回收的功能为垃圾回收。

圾回收机制,采用三色标记法:

  • 灰:遍历的时候为黑色。
  • 白:初始化为白色。
  • 黑:遍历时自身为黑色。

背景知识

什么是GC?

垃圾回收(Garbage Collection,缩写为GC),是一种自动内存管理机制。

在程序中定义一个变量后,会在内存中开辟相应空间进行存储。当不需要此变量后,需要手动销毁此对象,并释放内存。

对这种不再使用的内存资源进行自动回收的功能为垃圾回收。

GC 相关术语

GC的行话,先普及一下,不然后文读起来会稍微有点懵。

赋值器:说白了就是你写的程序代码,在程序的执行过程中,可能会改变对象的引用关系,或者创建新的引用。

回收器:垃圾回收器的责任就是去干掉那些程序中不再被引用得对象。

STW:全称是stop the word,GC期间某个阶段会停止所有的赋值器,中断你的程序逻辑,以确定引用关系。

举个栗子,有一个大院,孩子特别多,老师希望他们以班长为起点手牵手在一起,但总有几个不听话的孩子,没有牵手,你为了找出这些不听话的孩子,你会以班长为起点,一个一个的往后捋。但是如果有一个名叫张三的孩子,之前在队尾,后来在你数到队伍中间的时候,又跑到了队头和班长牵手去了,当你数完后,因为没有统计到张三,你就认为张三没有听话,没有奖励小红花,岂不让孩子比窦娥还冤…,所以这种情况下,你需要先让孩子们不动【映射到程序的概念,即STW停止程序运行】,然后再统计。

root对象:根对象是指赋值器不需要通过其他对象就可以直接访问到的对象,通过Root对象, 可以追踪到其他存活的对象。

常见的 root 对象有:

  • 全局变量:程序在编译期就能确定的那些存在于程序整个生命周期的变量。
  • 执行栈:每个 goroutine (包括main函数)都拥有自己的执行栈,这些执行栈上包含栈上的变量及堆内存指针。【堆内存指针即在gorouine中申请或者引用了在堆内存的变量】

Go 的 GC 发展演变史

v 1.3-标记清除法

标记清除法主要包含两个步骤:

  • 标记
  • 清除

示例如下:

开启 STW,停止程序的运行,图中是本次GC涉及到的root节点和相关对象。


从根节点出发,标记所有可达对象。


停止STW,然后回收然后回收所有未被标记的对象。


标记清除法的最大弊端就是在整个GC期间需要STW,将整个程序暂停。因为如果不进行STW的话,会出现已经被标记的对象A,引用了新的未被标记的对象B,但由于对象A已经标记过了,不会再重新扫描A对B的可达性,从而将B对象当做垃圾回收掉。

说实话这种全程STW的GC算法真的是如过街老鼠,人见人打…好家伙,让我程序停下来,专门去做垃圾回收这件事,在追求高性能的今天,很难有人可以接受这种性能损耗。

所以Golang团队这个时期就开始专注于如何能提升GC的性能,这里希望各位道友能明白Golang团队对GC算法优化的方向是什么,或者目标是什么,那就是让GC和用户程序可以互不干扰,并发进行。所以才有了后面的三色标记法。

v1.5 三色标记法

三色标

1、初始时,所有对象被标记为白色
2、GC开始,遍历rootset,将直接可达的对象标记为灰色
3、遍历灰色对象,将直接可达对象标记为灰色,并将自身标记为黑色
4、重复第3步,直到标记完所有的对象
5、将标记为白色的对象当做垃圾回收掉

总结

Golang v1.3之前采用传统采取标记-清除法,需要STW,暂停整个程序的运行。

在v1.5版本中,引入了三色标记法和插入写屏障机制,其中插入写屏障机制只在堆内存中生效。但在标记过程中,最后需要对栈进行STW。

在v1.8版本中结合删除写屏障机制,推出了混合屏障机制,屏障限制只在堆内存中生效。避免了最后节点对栈进行STW的问题,提升了GC效率。

Redis

Redis hash 冲突

Redis hash冲突是指在Redis中存储数据时,由于哈希函数的不同,可能会出现多个键映射到同一个哈希桶的情况,这种情况就是Redis hash冲突。