一,golang语言的数组与切片的区别
答:数组是一个固定长度的基本数据结构类型,slice是基于数组实现的一种长度可变的数据结构常用类型
具体区别如下:
1,创建方式不同,数组如abc := [...]int{1, 2, 3, 4},slice如abc:=[]int{1, 2, 3,4} 或者 abc:=make([]int, 4, 10)
2,传递方式不同,数组是值传递,slice是引用传递
3,底层结构不同,数组基本数据类型,slice header的底层结构如下
type SliceHeader struct {
Data uintptr
Len int
Cap int
}
其中SliceHeader是slice的运行时表示形式
参考:https://golang.org/ref/spec#Array_types
https://blog.golang.org/slices-intro
https://golang.org/pkg/reflect/#SliceHeader
二,golang的map类型与C++的map类型区别
1, golang的map类型,仅仅是一个哈希表,具体结构如下:
// A header for a Go map.
type hmap struct {
// Note: the format of the hmap is also encoded in cmd/compile/internal/gc/reflect.go.
// Make sure this stays in sync with the compiler's definition.
count int // # live cells == size of map. Must be first (used by len() builtin)
flags uint8
B uint8 // log_2 of # of buckets (can hold up to loadFactor * 2^B items)
noverflow uint16 // approximate number of overflow buckets; see incrnoverflow for details
hash0 uint32 // hash seed
buckets unsafe.Pointer // array of 2^B Buckets. may be nil if count==0.
oldbuckets unsafe.Pointer // previous bucket array of half the size, non-nil only when growing
nevacuate uintptr // progress counter for evacuation (buckets less than this have been evacuated)
extra *mapextra // optional fields
}
参考:https://golang.org/src/runtime/map.go
其中特性如下:
申请必须make,或者使用:=方式,直接使用未初始化的map会panic;
引用类型;
key的类型必须是可比较类型;
并发使用不安全,需要加sync.RWMutex锁;
不指定迭代顺序,若需要顺序,则需要另外申请slice保存keys,然后sort.Ints(keys);
参考:https://blog.golang.org/maps
2,C++的map的底层结构是红黑树
三,close通道关闭之后,Println输出之后的值,是否可以写入
1,close通道关闭之后,执行fmt.Println(<- ch),若ch还有值,则输出ch的值,否则输出ch类型的默认值
2,若写入close的通道,则会panic,报错:panic: send on closed channel
四,简述chan类型
1,贯穿go语言的设计思想:不要通过共享内存来通信,而应该通过通信来共享内存。
2,类似一个先进先出队列;其中buf是一个循环链表结构;lock是互斥锁,所以线程安全;
3,分为无缓冲通道和有缓存通道,无缓冲通道必须先有消费协程,然后再可发送数据;
4,关闭chan,使用close,关闭之后不可再写入,但可以消费chan中的数据。
五,defer/panic/recover/return的理解
1,defer延迟执行
2.1 在函数return之前可以执行的一段代码,常用于资源释放,解锁,增加recover等操作。
2.2 多个defer执行顺序LIFO
例子:
package main
import (
"fmt"
"errors"
)
func main() {
fmt.Printf("with named param, x: %d\n", namedParam())
fmt.Printf("error from err1: %v\n", err1())
}
func namedParam() (x int) {
x = 1
defer func() { x = 2 }()
return x
}
func err1() error {
var err error
defer func() {
if r := recover(); r != nil {
err = errors.New("recovered")
}
}()
panic(`foo`)
return err
}
输出:
with named param, x: 2
error from err1:
2,panic恐慌
2.1 内置函数,出现致命错误可以使用panic退出程序,如:panic("has error")
3,recover恢复
3.1 内置函数,重新获得panicking协程的控制,防止程序崩溃。注意像fatal的错误无法捕获,如map的并发读写:fatal error: concurrent map read and map write;死锁:fatal error: all goroutines are asleep - deadlock(申请无缓冲chan,直接生产数据会报此致命错误)
举例如下:
package main
import (
"fmt"
)
func main() {
defer fmt.Println("recover first ", recover())
defer func() {
if r := recover(); r != nil {
fmt.Println("recover ", r)
}
}()
defer fmt.Println("recover second ", recover())
panic("has error")
}
输出:
recover second
recover has error
recover first
4,return函数返回
4.1 return操作不是原子操作,过程具体可以分为三步:
首先赋值,把return的值赋给返回值
其次检查defer操作,有则调用,所以defer里面的函数是可以更改返回值的
最后返回返回值
六,简述内存逃逸,遇没有遇见过内存溢出及内存泄漏
1,内存逃逸
1.1 golang的内存分配逃逸于栈和堆
1.2 查看逃逸分析日志命令:go build -gcflags=-m
1.3 逃逸场景:指针逃逸,返回局部变量指针;大对象逃逸,申请局部变量是大对象的时候;动态类型,申请不确定大小内存的局部变量逃逸;闭包引用对象逃逸;
1.4 分析内存逃逸好处:可减少gc的压力,不逃逸则函数结束可以回收清理,不需要gc标识清理过程(所以指针传递不一定比值传递效率高);栈上分配内存效率更高(不是绝对,栈也会扩容缩容);静态分析,编译时完成(不影响性能);
1.5 举个例子如下:
package main
import "fmt"
func fibo() func() int {
a, b := 0, 1
fmt.Println("first a, b", a, b)
return func() int {
a, b = b, a+b
return a
}
}
func main() {
f := fibo()
for i := 0; i < 6; i++ {
fmt.Printf("fib: %d\n", f())
}
f1 := fibo()
fmt.Printf("fib1: %d\n", f1())
fmt.Println("f:%p", &f, ", f1:%p", &f1)
}
运行输出:
first a, b 0 1
fib: 1
fib: 1
fib: 2
fib: 3
fib: 5
fib: 8
first a, b 0 1
fib1: 1
f:%p 0xc00000e028 , f1:%p 0xc00000e038
另外可以分析逃逸情况,输入命令:go build -gcflags=-m
输出如下:
# test/t_memory_escape
./main.go:9:13: inlining call to fmt.Println
./main.go:11:9: can inline fibo.func1
./main.go:27:13: inlining call to fmt.Printf
./main.go:33:12: inlining call to fmt.Printf
./main.go:35:13: inlining call to fmt.Println
./main.go:7:2: moved to heap: a
./main.go:7:5: moved to heap: b
./main.go:9:14: "first a, b" escapes to heap
./main.go:9:14: a escapes to heap
./main.go:9:14: b escapes to heap
可以看见a/b俩变量都逃逸了
2,内存溢出
2.1 golang的内存溢出基本没有见过,因为他的堆栈信息是可以扩容的,初始栈的容量是2k。
3,内存泄漏
3.1 golang的内存管理是使用gc机制自动化管理,使用方法是标记清理法,具体到三色标记法,另外使用屏障等技术提高回收效率。
3.2 golang是存在内存泄漏的,如文件/套接字等句柄没有关闭释放。这个没有释放的句柄会堆积在内存,gc并不会回收他们,导致内存泄漏。也有可能是启动的goroutine没有按预期退出,导致协程泄漏。
七,有没有使用过pprof/Benchmark工具
1,pprof工具
1.1 pprof工具是golang自带的性能分析神器
1.2 查看堆栈信息:go tool pprof http://localhost:6060/debug/pprof/heap
1.3 查看cpu信息:go tool pprof http://localhost:6060/debug/pprof/profile?seconds=30
1.4 查看goroutine信息:go tool pprof http://localhost:6060/debug/pprof/goroutine
1.5 查看trace路径信息:go tool pprof http://localhost:6060/debug/pprof/trace
2,Benchmark工具
1.1 性能测试工具
1.2 基准测试,需要_test.go结尾
八,简述golang语言的GMP机制
1,G是goroutine的缩写,用来表示协程;M是machine的缩写,用来表示线程,可以设置最大数量:SetMaxThreads;P是processor的缩写,用来表示逻辑处理器,p的个数配置GOMAXPROCS;
2,p队列有两种,一种全局队列,平衡多个p队列之间的任务,有锁;一种本地队列,无数据竞争,速度快,无锁;
3,启动方式,首先确定p的个数,若有设置环境变量$GOMAXPROCS或者是runtime.GOMAXPROCS()则按设置数量,否则使用默认值cpu核心数。其次动态调整m的数量,若没有足够的数量的m关联p中可运行的g的时候会新创m,如所有的m被阻塞时。
4,调度设计策略,首先当m无g可运行时,会优先从其他p本地队列盗取g来运行,本地队列没有,才会盗取全局队列;其次当本地m运行的g,因进行系统调用而阻塞时,m会释放绑定的p,p会转移到其他空闲的m上运行其他的g;最后go1.14及之后,属于基于信号的抢占式调度,一个g不能无限占用cpu时间,占用一定时间是可以被抢占的,防止其他g饿死;
参考:https://learnku.com/articles/41728
九,简述golang语言的gc机制
1,golang的gc(garbage collection)机制,使用方法是标记清理法,具体到三色标记法,另外使用屏障等技术提高回收效率。
十,简述golang语言cgo原理
1,cgo主要是用来创建go语言包调用c代码,详细可见:https://golang.org/pkg/cmd/cgo/