make和new的区别
make和new都是golang用来分配内存的內建函数 make即分配内存,也初始化内存,用于引用类型(map,chan,slice)的创建,返回引用类型的本身。 new只是将内存清零,并没有初始化内存,new创建的是指针类型,new可以分配任意类型的数据,返回的是指针。
context的使用场景和用途
- 上下文控制
- 多个goroutine之间的数据交互等
- 超时控制:到某个时间点超时,过多久超时。
Go的Context的数据结构包含Deadline,Done,Err,Value,Deadline方法返回一个time.Time,表示当前Context应该结束的时间,ok则表示有结束时间,Done方法当Context被取消或者超时时候返回的一个close的channel,告诉给context相关的函数要停止当前工作然后返回了,Err表示context被取消的原因,Value方法表示context实现共享数据存储的地方,是协程安全的。
init函数的特征
- 一个包下可以有多个init函数,每个文件也可以有多个init 函数,多个 init 函数按照它们的文件名顺序逐个初始化、
- 应用初始化时初始化工作的顺序是,从被导入的最深层包开始进行初始化,层层递出最后到main包。但包级别变量的初始化先于包内init函数的执行。
- 不管包被导入多少次,包内的init函数只会执行一次。
原子操作(atomic)、互斥锁、自旋锁
- 原子操作是针对某个值的单个互斥操作,Go语言的标准库代码包sync/atomic提供了原子的读取(Load为前缀的函数)或写入(Store为前缀的函数)某个值
- 互斥锁是一种数据结构,用来让一个线程执行程序的关键部分,完成互斥的多个操作
- mutex是悲观锁,sync.Mutex有两种模式,正常模式和饥饿模式。
- 自旋锁,程将循环等待,然后不断地判断是否能够被成功获取,直到获取到锁才会退出循环,mutex 会让当前的 goroutine 去空转 CPU,在空转完后再次调用 CAS 方法去尝试性的占有锁资源,直到不满足自旋条件,则最终会加入到等待队列里。
channel、select、defer
- channel:无缓冲和有缓冲区别,没有缓冲区,从管道读数据会阻塞,管道有缓冲区但缓冲区没有数据或者管道满了,从管道读取数据也会阻塞。Channel被设计用来实现协程间通信的组件,其作用域和生命周期不仅限于某个函数内部,golang直接将其分配在堆上。
- select为golang提供了多路IO复用机制,select结构组成主要是由case语句和执行的函数组成。
- 每个defer语句都对应一个_defer实例,多个实例使用指针连接起来形成一个单连表,保存在gotoutine数据结构中,每次插入_defer实例,均插入到链表的头部,申请资源后立即使用defer关闭资源是个好习惯。
Go中主协程用WaitGroup等待其余协程退出
- 通过有缓冲的channel实现其阻塞等待一组协程结束,这个不能保证一组goroutine按照顺序执行,可以并发执行协程。
- Go里面能通过无缓冲的channel实现其阻塞等待一组协程结束,这个能保证一组goroutine按照顺序执行,但是不能并发执行。
sync.WaitGroup只有3个方法,Add()是添加计数,Done()减去一个计数,Wait()阻塞直到所有的任务完成
golang中的面向对象
-
面向对象的三大特征是:封装、继承和多态
-
封装:
- Go 语言使用结构体对属性进行封装,结构体就像是类的一种简化形式
- 在 Go 语言中,方法是作用在接收者(receiver)上的一个函数,接收者是某种类型的变量
- 名称首字母的大小写决定了该变量/常量/类型/接口/结构/函数……能否被外部包导入
- 无法被导入的字段可以使用 getter 和 setter 的方式来访问
- go不支持构造函数,一般提供一个输出的函数new,将所需的参数作为输入并返回新创建的结构体对象
-
继承
- Go 语言使用在结构体中内嵌匿名类型的方法来实现继承
-
多态
- 使用接口可以实现多态,接口在Go中是隐式地实现,如果类型为接口中声明的所有方法提供了定义,则该类型实现了这个接口
Go函数参数传递方式
Go的函数参数传递都是值传递,也可以引用传递
函数参数传递是值传递,为什么map,slice,chan可能在函数内被修改?
Go里面的map,slice,chan是引用类型。变量区分值类型和引用类型。
Go的slice底层数据结构和一些特性?
Go的slice底层数据结构是由一个array指针指向底层数组,len表示切片长度,cap表示切片容量。slice的主要实现是扩容(2倍和1.25倍)。
map和sync.Map
- map:不是线程安全的。在同一时间段内,让不同 goroutine 中的代码,对同一个字典进行读写操作是不安全
- sync.Map是并发安全的,纯使用原生map和互斥锁的方案相比,使用sync.Map可以显著地减少锁的争用。sync.Map本身虽然也用到了锁,但是,它其实在尽可能地避免使用锁。它们的算法复杂度与map类型一样都是O(1)的;
- 通过delete删除map的key,执行gc不会,内存没有被释放,如果通过map=nil,内存才会释放
- nil map是未初始化的map,空map是长度为空
golang中map是一个kv对集合。底层使用hash table,用链表来解决冲突
Go中 rune 类型
- rune类型实质其实就是int32,在处理字符串及其便捷的字符单位。它会自动按照字符独立的单位去处理方便我们在遍历过程中按照我们想要的方式去遍历。
- uint32最大值加1会溢出报错
不同的类型比较是否相等
- string,int,float interface等可以通过reflect.DeepEqual和等于号进行比较,
- slice,struct,map则一般使用reflect.DeepEqual来检测是否相等。
Go中uintptr和unsafe.Pointer的区别
- unsafe.Pointer是指针对象进行运算(也就是uintptr)的桥梁。
- unsafe.Pointer是通用指针类型,它不能参与计算,任何类型的指针都可以转化成 unsafe.Pointer,unsafe.Pointer 可以转化成任何类型的指针。
- uintptr 可以转换为 unsafe.Pointer,unsafe.Pointer 可以转换为 uintptr。uintptr是指针运算的工具,但是它不能持有指针对象(意思就是它跟指针对象不能互相转换)
协程,线程,进程的区别
- 进程:是应用程序的启动实例,每个进程都有独立的内存空间,不同的进程通过进程间的通信方式来通信。
- 线程:从属于进程,每个进程至少包含一个线程,线程是CPU调度的基本单位,多个线程之间可以共享进程的资源并通过共享内存等线程间的通信方式来通信。
- 协程:为轻量级线程,与线程相比,协程不受操作系统的调度,协程的调度器由用户应用程序提供,协程调度器按照调度策略把协程调度到线程中运行。
for range 的时候它的地址不会发生变化
在for a,b := range c 遍历中, a 和 b 在内存中只会存在一份,即之后每次循环时遍历到的数据都是以值覆盖的方式赋给 a 和 b,a,b 的内存地址始终不变。由于有这个特性,for循环里面如果开协程,不要直接把a或者b的地址传给协程。
安全读写共享变量的方式
- 将共享变量的读写放到一个 goroutine 中,其它 goroutine 通过 channel 进行读写操作。
- 可以用个数为 1 的信号量(semaphore)实现互斥
- 通过Mutex 锁实现
Go 分配内存的方式
Go程序启动的时候申请一大块内存,并且划分spans,bitmap,areana区域;arena区域按照页划分成一个个小块,span管理一个或者多个页,mcentral管理多个span供现场申请使用;mcache作为线程私有资源,来源于mcentral。
什么情况下内存会泄漏,定位排查内存泄漏问题
- go中的内存泄漏一般都是goroutine泄漏,就是goroutine没有被关闭,或者没有添加超时控制,让goroutine一只处于阻塞状态,不能被GC。
- 排查方式:一般通过pprof是Go的性能分析工具,在程序运行过程中,可以记录程序的运行信息,可以是CPU使用情况、内存使用情况、goroutine运行情况等,当需要性能调优或者定位Bug时候,这些记录的信息是相当重要。
参考地址