1、select是随机的还是顺序的?select的使用场景?
select会随机选择一个可用通道做收发操作.
常用语gorotine的完美退出
golang 的 select 就是监听 IO 操作,当 IO 操作发生时,触发相应的动作
每个case语句里必须是一个IO操作,确切的说,应该是一个面向channel的IO操作
2、Go语言局部变量分配在栈还是堆?深入分析
Go语言编译器会自动决定把一个变量放在栈还是放在堆.
编译器会做逃逸分析,当发现变量的作用域没有跑出函数范围,就可以在栈上,反之则必须分配在堆。
3、简述一下你对Go垃圾回收机制的理解?golang GC
v1.1 STW
v1.3 Mark STW, Sweep 并行【优化STW】
v1.5 三色标记法【初始白色--> 灰色待处理队列--> 标记为黑色--> STW(sweep黑色)】
v1.8 hybrid write barrier(混合写屏障:优化STW)
4、golang的协程调度GMP原理? 【线程模型 M:N】
- M(machine): 代表着真正的执行计算资源,可以认为它就是os thread(系统线程)。
- P(processor): 是协程调度器。P的数量可以通过GOMAXPROCS设置。
- G(goroutine): 调度系统的最基本单位goroutine,存储了goroutine的执行stack信息、goroutine状态以及goroutine的任务函数等。相当于OS的进程控制块PCB;它包含:函数执行的指令和参数,任务对象,线程上下文切换,字段保护,和字段的寄存器。
调度器设计策略
1.复用线程:
- work stealing机制:当本线程无可运行的Goroutine时,尝试从其他线程绑定的P队列偷取G,而不是消毁线程。
- hand off机制:当本线程因为Goroutine进行系统调用阻塞时,线程释放绑定的P,把P转移给其它空闲的线程执行。
2.抢占:在goroutine中要等待一个协程主动让出CPU才执行下一个协程;在GO中,一个goroutine最多占用CPU 10ms, 防止其他goroutine被锁死。
3.利用并行:利用GOMAXPROCS设置P数量,最多有GPMAXPROCS个线程分布在多个CPU上同时执行。
4.全局G队列:当M执行work stealing从其它P的本地队列中偷不到G时,它可以从全局列队获取G.
5.自旋线程:
5、介绍下 golang 的 runtime 机制?深入分析
Runtime 负责管理任务调度,垃圾收集及运行环境。同时,Go提供了一些高级的功能,如goroutine, channel, 以及GC。
这些高级功能需要一个runtime的支持. runtime和用户编译后的代码被linker静态链接起来,形成一个可执行文件。
这个文件从操作系统角度来说是一个user space的独立的可执行文件。
从运行的角度来说,这个文件由2部分组成,一部分是用户的代码,另一部分就是runtime。
runtime通过接口函数调用来管理goroutine, channel及其他一些高级的功能。从用户代码发起的调用操作系统API的调用都会被runtime拦截并处理。
Go runtime的一个重要的组成部分是goroutine scheduler。他负责追踪,调度每个goroutine运行,实际上是从应用程序的process所属的thread pool中分配一个thread来执行这个goroutine。
因此,和java虚拟机中的Java thread和OS thread映射概念类似,每个goroutine只有分配到一个OS thread才能运行。
6、如何获取 go 程序运行时的协程数量, gc时间, 对象数, 堆栈信息?
调用接口runtime.ReadMemStats可以获取以上所有信息, 注意: 调用此接口会触发 STW(Stop The World)
7、介绍下你平时都是怎么调试 golang 的 bug 以及性能问题的?
panic 调用栈 GDB
pprof
火焰图(配合压测)
使用go run -race 或者 go build -race 来进行竞争检测
查看系统 磁盘IO/网络IO/内存占用/CPU 占用(配合压测)
8、make 和 new 的区别
- make和new都是golang用来分配内存的內建函数,且在堆上分配内存.
- make 即分配内存,也初始化内存。new只是将内存清零,并没有初始化内存。
- new(T)是为一个T类型的新值分配空间, 并将此空间初始化为T的零值, 并返回这块内存空间的地址, 也就是T类型的指针T, 该指针指向T类型值占用的那块内存.
- new 分配返回的是指针,即类型 *Type。make 返回引用,即 Type。
- make(T)返回的是初始化之后的T, 且只能用于slice, map, channel三种类型.
- make(T, args) 返回初始化之后T类型的值, 且此新值并不是T类型的零值, 也不是T类型的指针T, 而是T类型值经过初始化之后的引用.
9、主协程如何等其余协程完再操作
使用channel进行通信,context,select
10、实现set
type inter interface{}
type Set struct {
m map[inter]bool
sync.RWMutex
}
func New() *Set {
return &Set{
m: map[inter]bool{},
}
}
func (s *Set) Add(item inter) {
s.Lock()
defer s.Unlock()
s.m[item] = true
}
- defer在匿名返回值和命名返回值函数中的不同表现
func watShadowDefer(i int) (ret int) {
ret = i * 2
if ret > 10 {
ret := 10 // 新初始化了一个局部变量
defer func() {
ret = ret + 1 // defer 将当前作用域相关信息压栈
}()
}
return //100。知识点:变量作用域和defer 返回值。
}
func main() {
result := watShadowDefer(50)
fmt.Println(result)
}
12、slice切片:是对数组一个连续片段的引用,是一种引用类型
- 切片提供了一个与指向数组的动态窗口
- Go 中数组赋值和函数传参都是值复制的,而切片是引用传递的
- 使用make创建切片:实现运行期动态指定数组长度,绕开编译期常量的限制
- 切片扩容策略:小于1024个元素,翻倍扩容。超过1024后1.25倍扩容。
// 切片定义
type slice struct {
array unsafe.Pointer
len int
cap int // cap一定要比len大
}
// 从 slice 中得到一块内存地址
s := make([]byte, 200)
ptr := unsafe.Pointer(s[0])
// 从go的内存地址中构造一个slice
var ptr unsafe.Pointer
var s1 = struct { //初始化一个结构体
addr unitptr
len int
cap int
}{ptr, length, length}
s := *(*[]byte)(unsafe.Pointer(s1)) // ptr 转 slice
13、go切片的排序与查找操作,排序操作在sort包中
sort.Ints对整数进行排序,
sort.Strings对字符串进行排序,
sort.Float64对浮点数进行排序
var a = [...]int{1,8,38,2,348,484} //数组是值类型,不能直接排序,必须转为切片
sort.Ints(a[:]) //SearchInts默认排序后的位置,一定要排序后在查找
index:=sort.SearchInts(a[:],348)
// 排序二维切片
intervals := [][]int{}
sort.Slice(intervals, func(i, j int) bool {
return intervals[i][0]<intervals[j][0]
})
14、go函数不支持默认参数,不支持重载,不支持命名函数嵌套,匿名函数可以嵌套
- Go函数从实参到形参的传递永远是值拷贝,,有时函数调用后实参指向的值发生了变化,是因为参数传递的是指针的拷贝,实参是一个指针变量,传递给形参的是这个指针变量的副本,实质上仍然是值拷贝;
- 闭包
package main
import "fmt"
func Accumulate(a int) func(b int) int {
return func(b int) int {
return a + b
}
}
func main() {
accAdd := Accumulate(1)
fmt.Println(accAdd(2)) // 3
fmt.Println(accAdd(4)) // 5
}
15、在golang中可以给任意类型(除指针)添加相应方法
type Interger int
func (i Interger) Less (j Interger) bool {
return i < j
}
func main() {
var i Interger = 1
fmt.Print(i.Less(5))
}
17 json与golang类型对应关系
bool, for JSON booleans
float64, for JSON numbers
string, for JSON strings
[]interface{}, for JSON arrays
map[string]interface{}, for JSON objects
nil for JSON null
http请求时需要注意【不要连接完,立马defer】
resp, err := http.Get(url)
// 先判断操作是否成功
if err != nil {
return err
}
// 如果操作成功,再进行Close操作
defer resp.Body.Close()
fallthrough 关键字的作用?
执行完fallthrough后直接跳到下一个条件语句,本条件执行语句后面的语句不执行