# 基础相关

# map怎么实现顺序读取

把map中的key通过sort包排序

# 两个nil可能不相等吗

Go中两个Nil可能不相等。

接口(interface) 是对非接口值(例如指针,struct等)的封装,内部实现包含 2 个字段,类型 T 和 值 V。一个接口等于 nil,当且仅当 T 和 V 处于 unset 状态(T=nil,V is unset)。

两个接口值比较时,会先比较 T,再比较 V。 接口值与非接口值比较时,会先将非接口值尝试转换为接口值,再比较。

i == pp == nil
i != nil

# Go函数返回局部变量的指针是否安全

在 Go 中是安全的,Go 编译器将会对每个局部变量进行逃逸分析。如果发现局部变量的作用域超出该函数,则不会将内存分配在栈上,而是分配在堆上

# 什么是零值

go语言中的零值是变量没有做初始化时系统默认设置的值。

var b bool // bool型零值是false var s string // string的零值是"" var a *int var a []int var a map[string] int var a chan int var a func(string) int var a error // error是接口 // 以上六种类型零值常量都是nil

所有其他数值型的类型(包括complex64/128)零值都是0,可以用常量表达式代表数值0的任何形式表示出来。

对于以上各种类型都可以通过==条件判断是不是零值:

但是类型不能混用,变量类型和零值类型必须匹配。

但是类型不能混用,变量类型和零值类型必须匹配。

结构也有零值。如果所有(递归的)字段都是零值,那么整个结构就是零值。但是没有零值常量用来表示某个结构的零值,所以也就无法用判断语句来识别一个结构是否处于零值。而且零值状态的结构也没有一个通用的语义,处于零值状态的结构可能意味着没有初始化,也可能是一个正常有用的状态。比如sync.Mutex零值状态就是处于没有锁住状态,是有意义的。所以不需要结构的零值常量

数组和结构类似,有零值,但是没有相应的零值常量。

string的零值是"",也可以用len(x)==0 来判断零值字符串。但是用""更好一点,把len(x)==0留给slice用。

# 关键字相关

# Go的Struct能不能比较

  • 相同struct类型的可以比较
  • 不同struct类型的不可以比较,编译都不过,类型不匹配

# select可以用于什么

Golang 的 select 机制可以理解为是在语言层面实现了和 select, poll, epoll 相似的功能:监听多个描述符的读/写等事件,一旦某个描述符就绪(一般是读或者写事件发生了),就能够将发生的事件通知给关心的应用程序去处理该事件。 golang 的 select 机制是,监听多个channel,每一个 case 是一个事件,可以是读事件也可以是写事件,随机选择一个执行,可以设置default,它的作用是:当监听的多个事件都阻塞住会执行default的逻辑。

goroutine作为Golang并发的核心,我们不仅要关注它们的创建和管理,当然还要关注如何合理的退出这些协程,不(合理)退出不然可能会造成阻塞、panic、程序行为异常、数据结果不正确等问题。goroutine在退出方面,不像线程和进程,不能通过某种手段强制关闭它们,只能等待goroutine主动退出。

# defer相关

第一个情况:defer延迟函数调用的fmt.Println(a)函数的参数值在defer语句出现时就已经确定了,所以无论后面如何修改a变量都不会影响延迟函数。 所以打印结果为1

第二个情况:defer延迟函数调用的函数参数的值在defer定义时候就确定了,而defer延迟函数内部所使用的值需要在这个函数运行时候才确定。

# 内存管理相关

# Golang的内存模型中为什么小对象多了会造成GC压力

go里面的垃圾回收用的是三色法,通常小对象过多会导致GC三色法消耗过多的GPU。优化思路是,减少对象分配.

# GC的触发条件

Go中对 GC 的触发时机存在两种形式:

runtime.GCGCGCGCGC

# GPM调度

# GPM调度模型中P里面g0的作用

在Go中 g0作为一个特殊的goroutine,为 scheduler 执行调度循环提供了场地(栈)。对于一个线程来说,g0 总是它第一个创建的 goroutine。

之后,它会不断地寻找其他普通的 goroutine 来执行,直到进程退出。

当需要执行一些任务,且不想扩栈时,就可以用到 g0 了,因为 g0 的栈比较大。

goroutinedeferproc_defer

# GO如何打印堆栈

有两个方法,第一个是runtime.Stack库

第二个是runtime/debug 库

参考:

# 并发相关(重点)

# 协程、进程、线程的区别

  • 进程

进程是具有一定独立功能的程序关于某个数据集合上的一次运行活动,进程是系统进行资源分配和调度的一个独立单位。每个进程都有自己的独立内存空间,不同进程通过进程间通信来通信。由于进程比较重量,占据独立的内存,所以上下文进程间的切换开销(栈、寄存器、虚拟内存、文件句柄等)比较大,但相对比较稳定安全。

  • 线程

线程是进程的一个实体,线程是内核态,而且是CPU调度和分派的基本单位,它是比进程更小的能独立运行的基本单位.线程自己基本上不拥有系统资源,只拥有一点在运行中必不可少的资源(如程序计数器,一组寄存器和栈),但是它可与同属一个进程的其他的线程共享进程所拥有的全部资源。线程间通信主要通过共享内存,上下文切换很快,资源开销较少,但相比进程不够稳定容易丢失数据。

  • 协程

协程是一种用户态的轻量级线程,协程的调度完全由用户控制。协程拥有自己的寄存器上下文和栈。 协程调度切换时,将寄存器上下文和栈保存到其他地方,在切回来的时候,恢复先前保存的寄存器上下文和栈,直接操作栈则基本没有内核切换的开销,可以不加锁的访问全局变量,所以上下文的切换非常快。

# Golang中除了加Mutex锁以外还有哪些方式安全读写共享变量

Golang中Goroutine 可以通过 Channel 进行安全读写共享变量,还可以通过原子性操作进行.

# 无缓冲Chan的发送和接收是否同步

  • channel无缓冲时,发送阻塞直到数据被接收,接收阻塞直到读到数据。
  • channel有缓冲时,当缓冲满时发送阻塞,当缓冲空时接收阻塞。

# Golang中常用的并发模型

  • 通过channel来进行并发控制
  • 通过sync包中的WaitGroup实现并发控制
  • 使用context来实现并发控制

# 数据竞争问题如何解决

  • 使用互斥锁sync.Mutex
  • 通过CAS无锁并发

就是不使用锁来控制并发,CAS有三个操作数:内存值V、旧的预期值A、要修改的值B,当且仅当预期值A和内存值V相同时,将内存值修改为B并返回true,否则什么都不做并返回false。不过也有有问题

# 怎么查看Goroutine的数量

GOMAXPROCSMultiplexGOMAXPROCS

# Go中的锁有哪些

sync.Map
sync.Map

# Channel是同步的还是异步的

Channel是异步进行的

# Goroutine和线程的区别

1.从调度上看,goroutine的调度开销远远小于线程调度开销。

OS的线程由OS内核调度,每隔几毫秒,一个硬件时钟中断发到CPU,CPU调用一个调度器内核函数。这个函数暂停当前正在运行的线程,把他的寄存器信息保存到内存中,查看线程列表并决定接下来运行哪一个线程,再从内存中恢复线程的注册表信息,最后继续执行选中的线程。这种线程切换需要一个完整的上下文切换:即保存一个线程的状态到内存,再恢复另外一个线程的状态,最后更新调度器的数据结构。某种意义上,这种操作还是很慢的。

Go运行的时候包含一个自己的调度器,这个调度器使用一个称为一个M:N调度技术,m个goroutine到n个os线程(可以用GOMAXPROCS来控制n的数量),Go的调度器不是由硬件时钟来定期触发的,而是由特定的go语言结构来触发的,他不需要切换到内核语境,所以调度一个goroutine比调度一个线程的成本低很多。

2.从栈空间上,goroutine的栈空间更加动态灵活。

每个OS的线程都有一个固定大小的栈内存,通常是2MB,栈内存用于保存在其他函数调用期间哪些正在执行或者临时暂停的函数的局部变量。这个固定的栈大小,如果对于goroutine来说,可能是一种巨大的浪费。作为对比goroutine在生命周期开始只有一个很小的栈,典型情况是2KB, 在go程序中,一次创建十万左右的goroutine也不罕见(2KB*100,000=200MB)。而且goroutine的栈不是固定大小,它可以按需增大和缩小,最大限制可以到1GB

3.goroutine没有一个特定的标识。

在大部分支持多线程的操作系统和编程语言中,线程有一个独特的标识,通常是一个整数或者指针,这个特性可以让我们构建一个线程的局部存储,本质是一个全局的map,以线程的标识作为键,这样每个线程可以独立使用这个map存储和获取值,不受其他线程干扰。

goroutine中没有可供程序员访问的标识,原因是一种纯函数的理念,不希望滥用线程局部存储导致一个不健康的超距作用,即函数的行为不仅取决于它的参数,还取决于运行它的线程标识。

# goroutine的优雅退出方法

# 使用for-range退出

range能够感知channel的关闭,当channel被发送数据的协程关闭时,range就会结束,接着退出for循环。

# 使用select case ,ok退出

# 使用退出通道退出

使用一个专门的管道来退出

# go协程有问题怎么查找

go tool pprof -http=:8001 http://127.0.0.1:7899/debug/pprof/goroutine\?debug\=1

image.png

参考:

# 标准库相关

# JSON 标准库对 nil slice 和 空 slice 的处理是一致的吗

如果是nilslice,其实会解析为null,空slice会解析为[]

nil sliceslice

通常错误的用法,会报数组越界的错误,因为只是声明了slice,却没有给实例化的对象。

此时slice的值是nil,这种情况可以用于需要返回slice的函数,当函数出现异常的时候,保证函数依然会有nil的返回值。empty slice 是指slice不为nil,但是slice没有值,slice的底层的空间是空的,此时的定义如下:

nil sliceempty slice