No.4 腾讯,阿里,字节,优科面经(中-Golang基础)

本文涉及的产品
性能测试 PTS,5000VUM额度
简介: No.4 腾讯,阿里,字节,优科面经(中-Golang基础)

原文链接:

https://blog.csdn.net/qq_41385137/article/details/12749270

 第一篇博客主要介绍了面试的各个大厂里面的相关算法,其实算法在大厂中的作用还是挺大的,还很多方面几乎起到了决定性的作用,默认的就是算法挂了那么这场面试基本就挂了。除了算法之外,golang的一些基础知识也是蛮重要,下面对面试中问到的golang的相关知识进行汇总整理,并给出相关参考。(面试系列第二篇文章讲述golang基础,小盖在这里再次感谢@刘人彰的分享,认真看过下文才知道总结有多么用心和细致。)

一、golang基础

1.golang的GMP模型是什么?

这个问的概率也是蛮高,基本上大厂必问,GMP是三个单词的缩写,也叫PMG模型(有的面试官这么叫,阿里面试官这么叫),G-gorountine,M-machine,P-processor 。


  1. GPM 模型,有一个全局队列(Global Queue存放等待运行的 G还有一个 P 的本地队列:也是存放等待运行的 G,但数量有限,不超过 256 个。
  2. GPM 的调度流程从 go func()开始创建一个 goroutine,新建的 goroutine 优先保存在 P 的本地队列中如果 P 的本地队列已经满了,则会保存到全局队列中
  3. M 从 P 的队列中取一个可执行状态的 G 来执行,如果 P 的本地队列为空,就会从其他的 MP 组合偷取一个可执行的 G 来执行当 M 执行某一个 G 时候发生系统调用或者阻塞,M 阻塞,如果这个时候 G 在执行,runtime 会把这个线程 M 从 P 中摘除然后创建一个新的操作系统线程来服务于这个 P当 M 系统调用结束时,这个 G 会尝试获取一个空闲的 P 来执行,并放入到这个 P 的本地队列。
  4. 如果这个线程 M 变成休眠状态,加入到空闲线程中,然后整个 G 就会被放入到全局队列中。

(Tips: 关于 G,P,M 的个数问题,G 的个数理论上是无限制的但是受内存限制P 的数量一般建议是逻辑 CPU 数量的 2 倍M 的数据默认启动的时候是 10000,内核很难支持这么多线程数,所以整个限制客户忽略,M 一般不做设置,设置好 P,M 一般都是要大于 P, 这个直接背下来就行。)


2. golang的内存管理是怎么样的?

 度小满,腾讯,字节都问了,具体可以参考之前整理的沃享荟文章。Go语言内存管理 golang的内存管理蛮有意思的,把对象放在一个个的span中,不同大小的对象放在不同的span中,有64种Go语言内存划分的三个区域分别为:arena区,bitmap区,spans区。对象分配在arena区,基本存储单元为span。bitmap区记录了GC和对象是否包含指针的标志位,spans记录了span索引指针,便于查找

3.golang的测试方法,怎么用的,在哪个包下,有几种类型?

   致景科技问了这个题目,考察的还挺细致,go的测试方法包含:单元测试、性能测试,都是在testing这个包下面。单元测试是testing.T 和性能测试 testing.B。

 有几个约定需要遵守:

1.一般测试func TestXxx(*testing.T)

测试行必须Test开头,Xxx为字符串,第一个X必须大写的[A-Z]的字幕,一般Xxx为被测试方法的函数名。


2.性能测试func BenchmarkXxx(*testing.B)
性能测试用Benchmark标记,Xxx同上。


3. 测试文件名约定
go语言测试文件名约定规则是必须以_test.go结尾,放在相同包下。
举个栗子:这是一个主函数,定义了加法Add运算。

    package main
    import “fmt”
    func add(x, y int) int {
       return x + y
    }
    func main() {
    fmt.Println(add(1,2))
    }

一般测试怎么弄呢?

package main
import “testing”
func TestAdd(t *testing.T) {
if add(1,2) != 3 {
     t.Error(“test foo:Addr failed”)
} else {
    t.Log(“test foo:Addr pass”)
   }
}

运行的话go test就行了。

    package main
    import “testing”
    func BenchmarkAdd(b *testing.B) {
    for i := 0; i < b.N; i++ {
        add(1, 2)
       }
    }

性能测试怎么弄呢?

package main
import “testing”
func BenchmarkAdd(b *testing.B) {
for i := 0; i < b.N; i++ {
   add(1, 2)
  }
}

运行  go test -v -test.bench=”.*” -count=5

4.golang的make和new的区别?(必问)

  (Tips:几乎每个厂都会问一下这个,大概说出几条就行,现在面试官也不会太深问这个玩意,但是要知道。)


1. new 和 make 都是在堆上分配内存,内存逃逸不讲,当然也可以了解一下。
2.
new 对指针类型分配内存,返回值是分配类型的指针new也可以对 slice 、map、channel 分配内存;
3.
make 仅用于 slice、map和 channel 的初始化返回值为类型本身,而不是指针


5.golang中channel怎么用的,底层的结构,向已关闭的channel中读和向channel中写有何区别?

(阿里一面就问了这个问题,当时正好背下来了)
概括:底层结构需要描述出来,这个简单,buf,发送队列,接收队列,lock。


答:channel 的数据结构包含 qccount 当前队列中剩余元素个数,dataqsiz 环形队列长度,即可以存放的元素个数,buf 环形队列指针,elemsize 每个元素的大小,closed 标识关闭状态,elemtype 元素类型,sendx 队列下表,指示元素写入时存放到队列中的位置,recv 队列下表,指示元素从队列的该位置读出。recvq 等待读消息的 goroutine 队列,sendq 等待写消息的 goroutine 队列,lock 互斥锁,chan 不允许并发读写。
channel 的几个特点 :


1)、读写值 nil 管道会永久阻塞


2)、关闭的管道读数据仍然可以读数据


3)、往关闭的管道写数据会 panic


4)、关闭为 nil 的管道 panic


5)、关闭已经关闭的管道 panic


6.golang的select怎么用的?(竟然是HR面问的,有点牛)

 这个是阿里HR面问的一道题目,可能是当时HR为了筛选人员问了一道基础题吧。
      golang 中的 select 就是用来监听和 channel 有关的 IO 操作,当 IO 操作发生时,触发相应的动作。有点像系统IO中的select、poll、epoll中的select ,select 只能应用于 channel 的操作,既可以用于 channel 的数据接收,也可以用于 channel 的数据发送
如果 select 的多个分支都满足条件,则会随机的选取其中一个满足条件的分支执行。

select {
  case <- chan1:
    // 如果 chan1 成功读到数据,则进行该 case 处理语句
  case chan2 <- 1:
    // 如果成功向 chan2 写入数据,则进行该 case 处理语句
  default:
    // 如果上面都没有成功,则进入default处理流程
}


7. golang在什么情况下会发生内存泄漏?


  这个当时百度问了这个问题,我说发生内存泄漏的时候,就是对象不能被GC回收掉。面试官明显不是很满意。

   
   1.实际开发中更多的还是Goroutine引起的内存泄漏,因为Goroutine的创建非常简单,通过关键字go即可创建,由于开发的进度大部分程序猿只会关心代码的功能是否实现,很少会关心Goroutine何时退出如果Goroutine在执行时被阻塞而无法退出,就会导致Goroutine的内存泄漏,一个Goroutine的最低栈大小为2KB,在高并发的场景下,对内存的消耗也是非常恐怖的。

       2. 互斥锁未释放会造成内存泄漏。

    func main() {
      var str0 = "12345678901234567890"
      str1 := str0[:10]
    }

    3. time.Ticker 是每隔指定的时间就会向通道内写数据。作为循环触发器,必须调用 stop 方法才会停止,从而被 GC 掉,否则会一直占用内存空间。

    func main() {
      var str0 = "12345678901234567890"
      str1 := str0[:10]
    }

    4. 字符串的截取引发临时性的内存泄漏
以上代码,
会有10字节的内存泄漏,我们知道,str0和str1底层共享内存,只要str1一直活跃,str0 就不会被回收,10字节的内存被使用,剩下的10字节内存就造成了临时性的内存泄漏,直到str1不再活跃如果str0足够大,str1截取足够小,或者在高并发场景中频繁使用,那么可想而知,会造成临时性内存泄漏,对性能产生极大影响。

   
  5. 切片截取引起子切片内存泄漏

func main() {
  var s0 = []int{0, 1, 2, 3, 4, 5, 6, 7, 8, 9}
  s1 := s0[:3]
}

在Go中发现内存泄露有2种方法1.一个是通用的监控工具2.另一个是go pprof。

8.golang的性能调优工具?pprof?怎么用?

首先咱们来了解一个概念。profile:

   
  profile 就是对应用的画像,这里画像就是应用使用 CPU 和内存等情况,也就是说应用使用了多少 CPU 资源、都是哪些部分在使用、每个函数使用的比例是多少、有哪些函数在等待 CPU 资源等等。知道了这些,我们就能对应用进行规划,也能快速定位性能瓶颈Golang 是一个对性能特别看重的语言,因此语言中自带了 profile 的库。golang在语言层面集成了profile采样工具,在程序运行过程中可以获取cpu、heap、block、traces等执行信息,这些会涉及到runtime/pprof、net/http/pprof、runtime/trace等package。一般情况下,获取profile数据最有两种形式:web形式与profile文件生成形式。


具体用法就是引入包 net/http/pprof,然后在代码中

package main
import (
  "log"
  "net/http"
  _ "net/http/pprof"
)
func main() {
  // 性能分析
  go func() {
    log.Println(http.ListenAndServe(":8080", nil))
  }()
  // 实际业务代码
  for {
    Add("test")
  }
}
func Add(str string) string {
  data := []byte(str)
  sData := string(data)
  var sum = 0
  for i := 0; i < 10000; i++ {
    sum += i
  }
  return sData
}

   其中,net/http/pprof包的init函数中注册了以下路由到http服务中,用浏览器打开http://localhost:8080/debug/pprof/,即可使用pprof提供的功能。


9.golang中map线程安全吗?sync.Map?怎么用?

   致景科技问了这个问题,现在的面试官都很精明了,为了防止大家背八股文,还会讲怎么用,哪里用了,真的是太难了。划重点!!!!map不是线程安全的。所以搞了一个sync.Map。

      原生的 go Map 在并发读写场景下经常会遇到 panic 的情况。造成的原因是 map 是非线性安全的,并发读写过程中 map 的数据会被写乱

     
而一般情况下,解决并发读写 map 的思路是加锁或者把一个 map 切分成若干个小 map,对 key 进行哈希在业界中使用最多并发指出的模式分别是:  ①原生 map + 互斥锁 或者 读写锁②  标准库 sync.Map (Go 1.9 及之后)。

       sync.Map的用法:声明后直接使用相应对的方法即可。

package main
import (
  "fmt"
  "sync"
)
func main() {
  var m sync.Map
  //Store
  m.Store(1,"a")
  m.Store(2,"b")
  //LoadOrStore
  //若key不存在,则存入key和value,返回false和输入的value
  v,ok := m.LoadOrStore("1","aaa")
  fmt.Println(ok,v) //false aaa
  //若key已存在,则返回true和key对应的value,不会修改原来的value
  v,ok = m.LoadOrStore(1,"aaa")
  fmt.Println(ok,v) //false aaa
  //Load
  v,ok = m.Load(1)
  if ok{
    fmt.Println("it's an existing key,value is ",v)
  } else {
    fmt.Println("it's an unknown key")
  }
  //Range
  //遍历sync.Map, 要求输入一个func作为参数
  f := func(k, v interface{}) bool {
    //这个函数的入参、出参的类型都已经固定,不能修改
    //可以在函数体内编写自己的代码,调用map中的k,v
      fmt.Println(k,v)
      return true
    }
  m.Range(f)
  //Delete
  m.Delete(1)
  fmt.Println(m.Load(1))
}

10. 说下GC三色标记算法?

   腾讯面试官问了这个问题,三色标记算法最初是在Java语言上出现的垃圾回收算法,该算法根据是否有对象引用把对象设置为黑,白,灰三种颜色。内容很多具体可以参考下面的文章。

参考文章:1.《JVM性能调优理论篇》2.《可达性分析之三色标记算法详解_黄智霖-blog的博客-CSDN博客_三色标记算法》


11.手写golang协程池?(百度就爱考这个,让我现场手写)

  没准备的话就很难写出来,毕竟时间有限,也不让查资料,所以提前准备好,背下来。没背下来怎么办?没背下来就挂了。这里给出一篇参考。

参考文章:
1. 《为什么需要协程池?
2. 《简单的协程池
3. 《go-playground/pool
4. 《ants(推荐)
5.《Golang学习篇——协程池_Word哥的博客-CSDN博客_协程池是什么


相关实践学习
通过性能测试PTS对云服务器ECS进行规格选择与性能压测
本文为您介绍如何利用性能测试PTS对云服务器ECS进行规格选择与性能压测。
相关文章
|
Cloud Native 程序员 Go
《阿里开发者手册-Golang专题》电子版地址
本期以 Go 语言为主题,收纳阿里巴巴资深程序员语青、丛霄、蜂翅、赋行、路德、冀锋 6 位的优秀技术文章。精解 Go 语言开发应用、常见技术难点以及产品创新实践等,帮助更多开发者快速掌握 Go 语言核心架构及功能,高效解决问题,提升研发效率,在云原生的浪潮下扬帆起航!
208 0
《阿里开发者手册-Golang专题》电子版地址
|
16天前
|
Go
Golang语言之管道channel快速入门篇
这篇文章是关于Go语言中管道(channel)的快速入门教程,涵盖了管道的基本使用、有缓冲和无缓冲管道的区别、管道的关闭、遍历、协程和管道的协同工作、单向通道的使用以及select多路复用的详细案例和解释。
47 4
Golang语言之管道channel快速入门篇
|
16天前
|
Go
Golang语言文件操作快速入门篇
这篇文章是关于Go语言文件操作快速入门的教程,涵盖了文件的读取、写入、复制操作以及使用标准库中的ioutil、bufio、os等包进行文件操作的详细案例。
39 4
Golang语言文件操作快速入门篇
|
16天前
|
Go
Golang语言之gRPC程序设计示例
这篇文章是关于Golang语言使用gRPC进行程序设计的详细教程,涵盖了RPC协议的介绍、gRPC环境的搭建、Protocol Buffers的使用、gRPC服务的编写和通信示例。
42 3
Golang语言之gRPC程序设计示例
|
16天前
|
安全 Go
Golang语言goroutine协程并发安全及锁机制
这篇文章是关于Go语言中多协程操作同一数据问题、互斥锁Mutex和读写互斥锁RWMutex的详细介绍及使用案例,涵盖了如何使用这些同步原语来解决并发访问共享资源时的数据安全问题。
38 4
|
16天前
|
Go
Golang语言错误处理机制
这篇文章是关于Golang语言错误处理机制的教程,介绍了使用defer结合recover捕获错误、基于errors.New自定义错误以及使用panic抛出自定义错误的方法。
37 3
|
16天前
|
Go 调度
Golang语言goroutine协程篇
这篇文章是关于Go语言goroutine协程的详细教程,涵盖了并发编程的常见术语、goroutine的创建和调度、使用sync.WaitGroup控制协程退出以及如何通过GOMAXPROCS设置程序并发时占用的CPU逻辑核心数。
23 4
Golang语言goroutine协程篇
|
16天前
|
Prometheus Cloud Native Go
Golang语言之Prometheus的日志模块使用案例
这篇文章是关于如何在Golang语言项目中使用Prometheus的日志模块的案例,包括源代码编写、编译和测试步骤。
20 3
Golang语言之Prometheus的日志模块使用案例
|
16天前
|
Go
Golang语言之函数(func)进阶篇
这篇文章是关于Golang语言中函数高级用法的教程,涵盖了初始化函数、匿名函数、闭包函数、高阶函数、defer关键字以及系统函数的使用和案例。
16 3
Golang语言之函数(func)进阶篇
|
16天前
|
Go
Golang语言之函数(func)基础篇
这篇文章深入讲解了Golang语言中函数的定义和使用,包括函数的引入原因、使用细节、定义语法,并通过多个案例展示了如何定义不返回任何参数、返回一个或多个参数、返回值命名、可变参数的函数,同时探讨了函数默认值传递、指针传递、函数作为变量和参数、自定义数据类型以及返回值为切片类型的函数。
18 2
Golang语言之函数(func)基础篇