go面试集锦1

【go面试集锦1】
目录

  • 1.go优缺点
  • 2.go中常量是怎么实现
  • 3.go的值传递和引用
  • 4.go struct能不能比较
  • 5.go协程线程安全吗
  • 6.go中关键字
  • 7.make和new区别
  • 8.defer
  • 9.生产者消费者模式,手写代码
  • 10.recover能处理所有的异常吗
  • 11.看你简历写着你了解RPC啊,那你说下RPC的整个过程?
  • 12.context包的用途
  • 13.client如何实现长连接
  • 14.slice,len,cap,共享,扩容
  • 15.map如何顺序读取
  • 16.实现set集合
  • 17.实现消息队列(多生产者,多消费者)
  • 19.TimeWait和CloseWait原因
  • 20.基本排序,哪些是稳定的
  • 21.Slice与数组区别,Slice底层结构
  • 22.Go的反射包怎么找到对应的方法
  • 23.反射的一些方法
  • 24.sync.Pool用过吗,为什么使用,对象池,避免频繁分配对象(GC有关),那里面的对象是固定的吗?
  • 25.字符串解析为数字(考虑浮点型)
  • 26.io模型,同步阻塞,同步非阻塞,异步
  • 27.cap理论
  • 28.go为什么高并发好?go的调度模型
  • 29.怎么理解go的interface
  • 30.怎么理解go的空interface
  • 31.Golang 中常用的并发模型
  • 32.JSON 标准库对 nil slice 和 空 slice 的处理是一致的吗
  • 33.Golang的内存模型,为什么小对象多了会造成gc压力
  • 34.说一下异步和非阻塞的区别?
  • 35.高可用软件是什么?
  • 36.配置中心如何保证一致性?
  • 37.分布式一直性原则

1.go优缺点
优点: 1.性能高,运行快。是python的30倍 2.它有很大的社区支持 3.天然支持并发,协程 4.可以直接操作指针 5.编译快 缺点: 1.类型不能自动转换 2.错误处理,啰嗦 3.包严重依赖于github

2.go中常量是怎么实现
const c1 int = 1000 常量,const在某块内存开辟一个变量空间,方便取值节省资源

3.go的值传递和引用
map和slice都是指针传递(引用),即函数内部是可以改变参数的值的 数组是值传递,不管函数内部如何改变参数,都是改变的拷贝值

4.go struct能不能比较
不能。 但是结构体中的成员变量可以比较

5.go协程线程安全吗
不安全,需要进行资源保护。 sync互斥锁,或者redis分布式锁

6.go中关键字
func 定义函数 interface 接口 select 监听协程 case 监听协程 defer 延迟 go 启动协程 map 字典 struct 结构体 chan 管道 goto 跳转 package 包声明 const 常量关键字 range 遍历 type 类型 import 导包 return 流程控制 var 新建变量 if 流程控制 for 流程控制 continue 流程控制 break流程控制 else 流程控制 switch 流程控制 default 流程控制 fallthrough 强制执行switch之后的case代码

7.make和new区别
new: 1.new只分配内存 2.new返回的是指向类型的指针 3.new可以分配任意类型的数据make: 1.make即分配内存,也初始化内存。 2.make返回的还是引用类型本身 3.make分配及初始化类型为slice,map,channel的数据。

8.defer
Go语言的 defer 语句会将其后面跟随的语句进行延迟处理,在 defer 归属的函数即将返回时,将延迟处理的语句按 defer 的逆序进行执行, 也就是说: 先被 defer 的语句最后被执行 最后被 defer 的语句,最先被执行。

9.生产者消费者模式,手写代码
package mainimport ( "fmt" "sync" "time" )var wg sync.WaitGroupfunc main() { wg.Add(2) ch := make(chan int) go func() { for i := 0; i < 10; i++ { ch <- i } close(ch) wg.Done() }() go func() { time.Sleep(time.Second*1) for x:=range ch{ fmt.Println(x) } wg.Done() }() defer fmt.Println("主线程结束") wg.Wait() //等待 }

10.recover能处理所有的异常吗
不能。 当然是不能的,我所知道的,Go 中的错误主要有三种,一个是业务相关的错误,即 error,一种是 panic,还有一种 Go 中的一些 fatal error。 panic:让程序崩溃的错误 recover只可以处理panic错误 recover可以拦截panic。它可以是当前的程序从运行时panic的状态中恢复并重新获得流程控制权。

11.看你简历写着你了解RPC啊,那你说下RPC的整个过程? go面试集锦1
文章图片

简单的说,RPC就是从一台机器(客户端)上通过参数传递的方式调用另一台机器(服务器)上的一个函数或方法(可以统称为服务)并得到返回的结果。1、服务消费者(client客户端)通过调用本地服务的方式调用需要消费的服务; 2、客户端存根(client stub)接收到调用请求后负责将方法、入参等信息序列化(组装)成能够进行网络传输的消息体; 3、客户端存根(client stub)找到远程的服务地址,并且将消息通过网络发送给服务端; 4、服务端存根(server stub)收到消息后进行解码(反序列化操作); 5、服务端存根(server stub)根据解码结果调用本地的服务进行相关处理; 6、本地服务执行具体业务逻辑并将处理结果返回给服务端存根(server stub); 7、服务端存根(server stub)将返回结果重新打包成消息(序列化)并通过网络发送至消费方; 8、客户端存根(client stub)接收到消息,并进行解码(反序列化); 9、服务消费方得到最终结果;

12.context包的用途
Context通常被译作上下文,它是一个比较抽象的概念,其本质,是【上下上下】存在上下层的传递,上层会把内容传递给下层。在Go语言中,程序单元也就指的是Goroutine原理说白了就是: 当前协程取消了,可以通知所有由它创建的子协程退出 当前协程取消了,不会影响到创建它的父级协程的状态 扩展了额外的功能:超时取消、定时取消、可以和子协程共享数据 这就是context包的核心原理,链式传递context,基于context构造新的context

13.client如何实现长连接
和短链接基本一样,只需要循环读取server端返回的response即可package mainimport ( "fmt" "io" "log" "net/http" )func main() { request, err := http.NewRequest("GET", "http://www.example.com/", nil) if err != nil { log.Fatal(err) }http_client := &http.Client{} response, err := http_client.Do(request) if err != nil { log.Fatal(err) }buf := make([]byte, 4096) // any non zero value will do, try '1'. for { n, err := response.Body.Read(buf) if n == 0 && err != nil { // simplified break }fmt.Printf("%s", buf[:n]) // no need to convert to string here } fmt.Println() }

14.slice,len,cap,共享,扩容
append函数,因为slice底层数据结构是,由数组、len、cap组成,所以,在使用append扩容时,会查看数组后面有没有连续内存快,有就在后面添加,没有就重新生成一个大的素组

15.map如何顺序读取
map不能顺序读取,是因为他是无序的,想要有序读取,首先的解决的问题就是,把key变为有序,所以可以把key放入切片,对切片进行排序,遍历切片,通过key取值。

16.实现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 }

17.实现消息队列(多生产者,多消费者)
channel+syncpackage mainimport ( "fmt" "sync" )// 写 func producer(out chan<-int){ for i:=0; i<10; i++{ out <-i } close(out) wg.Done() } //读 func consumer(data <-chan int){ for { x,ok := <-data if ok == false{ wg.Done() return } fmt.Println(x) } }var wg sync.WaitGroup func main() { ch:=make(chan int) wg.Add(2) go producer(ch) go consumer(ch) wg.Wait() }

19.TimeWait和CloseWait原因
大量超时和等待原因: 1.操作数据库没有主动close关闭连接 2.全局数据库db初始化没有初始化好,没有关闭 3.全局连接池没有复用,一直new pg连接和close连接 4.业务量增大连接池也要增大

20.基本排序,哪些是稳定的 go面试集锦1
文章图片

21.Slice与数组区别,Slice底层结构
var a [3]int// 数组 var a []int// 切片切片底层依附于数组。 //总结1:当切片追加值,超过了切片容量,切片容量会翻倍,在原来容量基础上乘以2 //总结2:一旦超过了原数组, 就会重新申请数组,把数据copy到新数组,切片和原数组就没有关系了 //总结3:追加值打破了原数据的容量,就不会再影响原数据1.18版本以前:容量小于1024翻倍,大于1024增加1/4 1.18版本:容量小于1024翻倍,大于1024cap=原切片长度+新增元素个数

22.Go的反射包怎么找到对应的方法
func Poni(o interface{}) { t := reflect.TypeOf(o) //获取类型 m := t.Method(0) fmt.Println(m.Name) // Hello fmt.Println(m.Type) //func(main.Users) }type Users struct { Idint Name string Ageint }// 绑方法 func (u Users) Hello() { fmt.Println("Hello word") }func main() { u := Users{1, "jeff", 18} Poni(u)}/////////////////// Hello func(main.Users)

23.反射的一些方法
t:=reflect.TypeOf(o) //获取类型 v:=reflect.ValueOf(o) //获取值 t.NumField() //获取结构体字段个数 t.Field(i) //取每个字段 v.Field(i).Interface() //获取字段对应的值v = v.Elem() // 获取指针指向的元素 fild := v.FieldByName("Name")// 取字段 v.FieldByName("Age").SetInt(20)//改年龄m := v.MethodByName("Hello") // 获取方法 name:=v.Field(1).Interface() // 反射获取结构体字段对应的值 args := []reflect.Value{reflect.ValueOf(name)}//构建参数 // 没参数的情况下:var args2 []reflect.Value m.Call(args) // 调用方法,需要传入方法的参数

24.sync.Pool用过吗,为什么使用,对象池,避免频繁分配对象(GC有关),那里面的对象是固定的吗?
pool就是对象缓存池,用来减少堆上内存的反复申请和释放的。因为 golang 的内存是用户触发申请,runtime 负责回收。如果用户申请内存过于频繁,会导致runtime 的回收压力陡增,从而影响整体性能。 有了pool 之后就不一样了,对象申请先看池子里有没有现成的,有就直接返回。释放的时候内存也不是直接归还,而是放进池子而已。适时释放。 这样就能极大的减少申请内存的频率。从而减少gc压力

25.字符串解析为数字(考虑浮点型)
k, _ := strconv.Atoi("135") // 整数到十进制 f, _ := strconv.ParseFloat("1.234", 64) // string到float64

26.io模型,同步阻塞,同步非阻塞,异步 IO操作与IO模型 27.cap理论
//总结1:当切片追加值,超过了切片容量,切片容量会翻倍,在原来容量基础上乘以2 //总结2:一旦超过了原数组, 就会重新申请数组,把数据copy到新数组,切片和原数组就没有关系了 //总结3:追加值打破了原数据的容量,就不会再影响原数据1.18版本以前:容量小于1024翻倍,大于1024增加1/4 1.18版本:容量小于1024翻倍,大于1024cap=原切片长度+新增元素个数

28.go为什么高并发好?go的调度模型
GMP: goroutine说到底其实就是协程,但是它比线程更小,几十个goroutine可能体现在底层就是五六个线程,Go语言内部帮你实现了这些goroutine之间的内存共享

29.怎么理解go的interface
Go语言中的接口很特别,而且提供了难以置信的一系列灵活性和抽象性。接口是一个自定义类型,它是一组方法的集合,要有方法为接口类型就被认为是该接口。从定义上来看,接口有两个特点: 接口本质是一种自定义类型,因此不要将Go语言中的接口简单理解为C++/Java中的接口,后者仅用于声明方法签名。 接口是一种特殊的自定义类型,其中没有数据成员,只有方法(也可以为空)。

30.怎么理解go的空interface
空接口(interface{})不包含任何的method,正因为如此,所有的类型都实现了interface{}。interface{}对于描述起不到任何的作用(因为它不包含任何的method),但是interface{}在我们需要存储任意类型的数值的时候相当有用,因为它可以存储任意类型的数值。它有点类似于C语言的void*类型。

31.Golang 中常用的并发模型
通过channel通知实现并发控制 通过sync包中的WaitGroup实现并发控制 在Go 1.7 以后引进的强大的Context上下文,实现并发控制

32.JSON 标准库对 nil slice 和 空 slice 的处理是一致的吗
首先JSON 标准库对 nil slice 和 空 slice 的处理是不一致.通常错误的用法,会报数组越界的错误,因为只是声明了slice,却没有给实例化的对象。var slice []int slice[1] = 0 此时slice的值是nil,这种情况可以用于需要返回slice的函数,当函数出现异常的时候,保证函数依然会有nil的返回值。empty slice 是指slice不为nil,但是slice没有值,slice的底层的空间是空的,此时的定义如下:slice := make([]int,0) slice := []int{} 当我们查询或者处理一个空的列表的时候,这非常有用,它会告诉我们返回的是一个列表,但是列表内没有任何值。总之,nil slice 和 empty slice是不同的东西,需要我们加以区分的.

33.Golang的内存模型,为什么小对象多了会造成gc压力
通常小对象过多会导致GC三色法消耗过多的GPU。优化思路是,减少对象分配。

34.说一下异步和非阻塞的区别?
异步和非阻塞的区别: 异步:调用在发出之后,这个调用就直接返回,不管有无结果;异步是过程。 非阻塞:关注的是程序在等待调用结果(消息,返回值)时的状态,指在不能立刻得到结果之前,该调用不会阻塞当前线程。同步和异步的区别: 同步:一个服务的完成需要依赖其他服务时,只有等待被依赖的服务完成后,才算完成,这是一种可靠的服务序列。要么成功都成功,失败都失败,服务的状态可以保持一致。 异步:一个服务的完成需要依赖其他服务时,只通知其他依赖服务开始执行,而不需要等待被依赖的服务完成,此时该服务就算完成了。被依赖的服务是否最终完成无法确定,一次它是一个不可靠的服务序列。消息通知中的同步和异步: 同步:当一个同步调用发出后,调用者要一直等待返回消息(或者调用结果)通知后,才能进行后续的执行。 异步:当一个异步过程调用发出后,调用者不能立刻得到返回消息(结果)。在调用结束之后,通过消息回调来通知调用者是否调用成功。阻塞与非阻塞的区别: 阻塞:阻塞调用是指调用结果返回之前,当前线程会被挂起,一直处于等待消息通知,不能够执行其他业务,函数只有在得到结果之后才会返回。 非阻塞:非阻塞和阻塞的概念相对应,指在不能立刻得到结果之前,该函数不会阻塞当前线程,而会立刻返回。 同步与异步是对应的,它们是线程之间的关系,两个线程之间要么是同步的,要么是异步的。阻塞与非阻塞是对同一个线程来说的,在某个时刻,线程要么处于阻塞,要么处于非阻塞。阻塞是使用同步机制的结果,非阻塞则是使用异步机制的结果。

35.高可用软件是什么?
高可用:保持其服务的高度可用性.比如mysql多主多从模式,其中一个主节点挂了没关系。其他节点立马顶上去,保证服务的高可用,mysql的哨兵是当主节点挂了,把其中一个从节点选为主节点。k8s集群也是,多个master

36.配置中心如何保证一致性?
使用k8s的configmap

37.分布式一直性原则 go面试集锦1
文章图片

C数据一致性:所有应用程序都能访问到相同的数据。 A数据可用性:任何时候,任何应用程序都可以读写访问。 P分区耐受性:系统可以跨网络分区线性伸缩。(通俗来说就是数据的规模可扩展) 在大型网站中通常都是牺牲C,选择AP。为了可能减小数据不一致带来的影响,都会采取各种手段保证数据最终一致。数据强一致:各个副本的数据在物理存储中总是一致的。 数据用户一致:数据在物理存储的各个副本可能是不一致的,但是通过纠错和校验机制,会确定一个一致的且正确的数据返回给用户。 数据最终一致:物理存储的数据可能不一致,终端用户访问也可能不一致,但是一段时间内数据会达成一致。

    推荐阅读