golang interview

https://www.kanzhun.com/search/?city=0&cityName=%E5%85%A8%E5%9B%BD&experience=0&industry=0&pageCurrent=11&q=golang&salary=0&type=interview

  • 先用go写了一个面向对象的例子,拥有get和set方法,导出

 - Go不是OOP的语言,但是又允许有OOP的编程风格。
 - 封装、继承、多态和抽象是面向对象编程的4个基本特征
 - type Animal interface {
	Move()
	Shout()
}

type Dog struct {
}

func (dog Dog) Move() {
	fmt.Println("A dog moves with its legs.")
}

func (dog Dog) Shout() {
	fmt.Println("wang wang wang.")
}

type Bird struct {
}

func (bird Bird) Move() {
	fmt.Println("A bird flys with its wings.")
}

func (bird Bird) Shout() {
	fmt.Println("A bird shouts.")
}

func main() {
	var animal Animal

	animal = Dog{}
	animal.Move()
	animal.Shout()

	animal = Bird{}
	animal.Move()
	animal.Shout()
}

  • go的指针和值,接收者,四次握手中time——wait出现的时机,mysql事务。

在这里插入图片描述
方法是在 func 和 notify 之间多了一个 user 类型的参数 u,这个 u 就称作接收者。
接收者有两种,一种是值接收者,一种是指针接收者。值接收者,是接收者的类型是一个值,是一个副本,方法内部无法对其真正的接收者做更改;指针接收者,接收者的类型是一个指针,是接收者的引用,对这个引用的修改之间影响真正的接收者
1.如果方法需要修改接受者,接受者必须是指针类型。
3.如果接受者是一个大的结构体或者数组,那么指针类型接受者更有效率

  • go struct能不能比较go defer(for defer)

    如果结构体的所有成员变量都是可比较的,那么结构体就可比较
    如果结构体中存在不可比较的成员变量,那么结构体就不能比较,
    map和切片不能比较,所以结构体实例也不能比较
    defer代码块函数会在函数return之后增加一个函数调用,通常 defer用来释放函数内部变量
    defer是先进后出,压入defer中的数据不会随后续代码中更改而更改
    和直接调用相比,defer的执行存在着额外的开销,例如defer会对其后需要的参数进行内存拷贝,还需要对defer结构进行压栈出栈操作。所以在循环for中定义defer可能导致大量的资源开销

  • select可以用于什么
    在这里插入图片描述break关键字可跳出select的执行。

  • go的调度问题

在这里插入图片描述

  • context包的用途
    在这里插入图片描述 ctx.Done()返回一个channel,当context关闭后,Done()返回一个被关闭的管道,关闭的管理仍然是可读的,据此goroutine可以收到关闭请求; 当context还未关闭时,Done()返回nil
    在这里插入图片描述

  • 主协程如何等其余协程完再操作
    1.channel进行通信
    2.context包 使用context中cancel函数,这种模式是主线程去通知子线程结束,select
    3.sync.WaitGroup模式

  • slice,len,cap,共享,扩容,实现set实现消息队列(多生产者,多消费者)

  • 大文件排序基本排序
    golang实现各种排序算法
    https://blog.csdn.net/mrs_len/article/details/54094390

  • 哪些是稳定的http get跟headhttp 401,403http keep-alivehttp能不能一次连接多次请求,不等后端返回
    从HTTP/1.1起,默认都开启了Keep-Alive,保持连接特性,简单地说,当一个网页打开完成后,客户端和服务器之间用于传输HTTP数据的TCP连接不会关闭,如果客户端再次访问这个服务器上的网页,会继续使用这一条已经建立的连接
    Keep-Alive不会永久保持连接,它有一个保持时间,可以在不同的服务器软件(如Apache)中设定这个时间

  • tcp与udp区别,udp优点,适用场景,time-wait的作用,数据库如何建索引孤儿进程,僵尸进程死锁条件,如何避免
    在这里插入图片描述首先 UDP 是不需要和 TCP一样在发送数据前进行三次握手建立连接的,想发数据就可以开始发送了。并且也只是数据报文的搬运工,不会对数据报文进行任何拆分和拼接操作
    不可靠性体现在无连接上,通信都不需要建立连接,想发就发,这样的情况肯定不可靠。并且收到什么数据就传递什么数据,并且也不会备份数据,发送数据也不会关心对方是否已经正确接收到数据了。

  • tcp可靠的原因
    [1] 确认和重传机制

建立连接时三次握手同步双方的“序列号 + 确认号 + 窗口大小信息”,是确认重传、流控的基础
传输过程中,如果Checksum校验失败、丢包或延时,发送端重传
[2] 数据排序

TCP有专门的序列号SN字段,可提供数据re-order
[3] 流量控制

窗口和计时器的使用。TCP窗口中会指明双方能够发送接收的最大数据量

  • linux命令,查看端口占用,cpu负载,内存占用,如何发送信号给一个进程
    cpu负载,内存占用 top
    lsof使用, lsof -i:port 查看某个端口是否被占用
  • git文件版本,使用顺序,merge跟rebase
  • mysql索引为什么要用B+树?mysql索引的存储结构
  • redis集群怎么搞的?
  • 变量逃逸
    变量逃逸就是一个变量由栈逃逸到堆的过程。
    我们知道c和c++变量的内存分配和销毁都是需要程序员手动来执行的。
    举个栗子,在函数内部定义了一个局部变量,然后返回这个局部变量的地址,这些局部变量是在栈上分配的(静态分配内存),一旦函数执行完成,变量占据的内存被销毁,任何这个返回值的动作,比如解引用,都会扰乱程序的执行,甚至导致程序崩溃。
    或者,我们使用new函数构造一个变量(动态分配内存),然后返回变量的地址,因为变量是在堆上创建的,所以函数执行完成退出时变量不会被销毁。但是调用者很可能忘记delete,也就会发生内存泄露。
    综上,变量的逃逸分析就是决定一个变量是分配在堆上还是栈上。
逃逸分析是为了合理的分配变量到它该去的地方,如果一个变量在函数执行完成后没有别的用处,那么就该分配到栈上,毕竟栈上的内存分配比堆上的快很多,相反,如果变量在函数结束后还有其他引用,那就该分配到堆上。
当然,如果变量都分配到堆上,堆不像栈可以自动清理,堆会引起go频繁的进行垃圾回收,而垃圾回收会占用比较大的系统开销。堆和栈相比,堆适合不可预知大小的内存分配,但分配速度较慢,而且会形成内存碎片,最后还需要gc进行释放。栈内存分配就比较快了,只需要CPU的两个命令即可实现分配和释放。
通过逃逸分析,可以尽量把哪些不需要分配到堆上的变量直接分配到栈上,堆上的变量减少了,会减轻分配堆内存的开销,同时也会减少gc的压力,提供程序的运行速度。

go编译器逃逸分析的基本原则是,若个一个函数返回对一个变量的引用,那么它就会发生逃逸。如果编译器对代码进行分析后,发现函数返回后,变量不在被引用,那么还是分配到栈上。

  • new和make区别
    内建函数 new 用来分配内存,它的第一个参数是一个类型,不是一个值,它的返回值是一个指向新分配类型零值的指针
    make它的定义比 new 多了一个参数,返回值也不同, make 用来为 slice,map 或 chan 类型分配内存和初始化一个对象(注意:只能用在这三种类型上),make 返回类型的引用而不是指针

  • slice怎么判断相等
    slice相等是长度相等,每个元素也相等
    1.reflect.DeepEqual
    2.循环遍历

  • golang包管理,比如值传递

  • 进程与线程的区别是什么,协程和线程的区别是什么
    对于用户层面来说,进程就是一块运行起来的程序,线程就是程序里的一些并发的功能。对于操作系统层面来说,标准回答是“进程是资源分配的最小单位,线程是cpu调度的最小单位”。
    虽然线程比进程要轻量级,但是每个线程依然占有1M左右的空间,在高并发场景下非常吃机器内存,比如构建一个http服务器,如果每来一次请求分配一个线程,请求数暴增容易OOM,而且线程切换的开销也是不可忽视的。同时,线程的创建与销毁同样是比较大的系统开销,因为是由内核来做的,解决方法也有,可以通过线程池或协程来解决。
    协程是用户态的线程,比线程更加的轻量级,操作系统对其没有感知,之所以没有感知是由于协程处于线程的用户栈能感知的范围,是由用户创建的而非操作系统
    如一个进程可拥有以有多个线程一样,一个线程也可以拥有多个协程。协程之于线程如同线程之于cpu,拥有自己的协程队列,每个协程拥有自己的栈空间,在协程切换时候只需要保存协程的上下文,开销要比内核态的线程切换要小很多。

  • golang并发模型GMP调度过程,M的大小,P的数量 (golang的协程调度机制–GMP)
    golang为什么这么快?归功于go的GMP调度模型
    golang为并发而生
    Goroutine非常轻量,主要体现在以下方面:

    上下文切换代价小,Goroutine的上下文切换只涉及到三个寄存器(PC/SP/DX)的值的修改,而线程的切换需要涉及 模式转换,以及16个寄存器的刷新。
    内存占用少,线程栈空间一般是2M,而goroutine只需要2k;

  • 在这里插入图片描述在这里插入图片描述在Go程序里,我们也可以通过下面的图示来展示G-M-P模型。
    在这里插入图片描述G 的数量可以远远大于 M 的数量,换句话说,Go 程序可以利用少量的内核级线程来支撑大量 Goroutine 的并发M:N模型。多个 Goroutine 通过用户级别的上下文切换来共享内核线程 M 的计算资源,但对于操作系统来说并没有线程上下文切换产生的性能损耗。

为了更加充分利用线程的计算资源,Go 调度器采取了以下几种调度策略:
1.任务窃取:
为了提高 Go 并行处理能力,调高整体处理效率,当每个 P 之间的 G 任务不均衡时,调度器允许从 GRQ,或者其他 P 的 LRQ 中获取 G 执行。
2.由于网络请求和 IO 操作导致 Goroutine 阻塞
Go 程序提供了网络轮询器(NetPoller)来处理网络请求和 IO 操作的问题,其后台通过 kqueue(MacOS),epoll(Linux)或 iocp(Windows)来实现 IO 多路复用。

通过使用 NetPoller 进行网络系统调用,调度器可以防止 Goroutine 在进行这些系统调用时阻塞 M。这可以让 M 执行 P 的 LRQ 中其他的 Goroutines,而不需要创建新的 M。有助于减少操作系统上的调度负载。

G1 正在 M 上执行,还有 3 个 Goroutine 在 LRQ 上等待执行。网络轮询器空闲着,什么都没干。

  • SLICE如何扩容
    slice这种数据结构便于使用和管理数据集合,可以理解为是一种“动态数组”
    扩容规则:
    1.如果切片的容量小于1024个元素,那么扩容的时候slice的cap就翻番,乘以2;一旦元素个数超过1024个元素,增长因子就变成1.25,即每次增加原来容量的四分之一。
    2.如果扩容之后,还没有触及原数组的容量,那么,切片中的指针指向的位置,就还是原数组,如果扩容之后,超过了原数组的容量,那么,Go就会开辟一块新的内存,把原来的值拷贝过来,这种情况丝毫不会影响到原数组。

  • golang 的 gc,groutine的底层实现
    golang中的gc采用三色标记法。在讲三色标记法之前,先了解一下Mark and Sweep算法,因为Mark and Sweep算法是三个标记法的一个改进版。
    Mark and Sweep算法: 停止运行程序,遍历所有被引用的变量,被引用的对象被标记为“被引用”,没有被标记的进行回收。内存单元并不会立刻回收对象,而是将其标记为“不可达”状态。直到到达某个阈值或者到达某个时间间隔后,对其进行垃圾回收。算法分为两部分:标记(Mark)和清理(Sweep)。挂起程序,对所有存活的内存单元进行扫描,标记,确定哪些可以清除。优点是解决了相互引用的缺点,不足是会停止应用程序,当内存单元变量过多时,扫描清除会花很长时间,甚至几百毫秒。
    三色标记法:属于标记清除法的一个改进版。起初所有内存对象都在白色的,从根出发标记所有的对象,标记为灰色,并放入一个队列。然后再将灰色队列中的对象取出,将其引用对象标记为黑色。重复此步骤,待灰色对象队列为空时,所有白色对象既为垃圾。最后白色区域内存对象被释放,黑色区域标记为白色,等待下一阶段GC再次扫描。

三色标记法触发有两个条件:第一个是阀值,就是内存扩大一倍时启动GC。第二个是每隔两分钟GC一次。

  • defer的问题
    1.即时的参数传递
    2.调用os.Exit() 时defer不会被执行(程序会立刻终止)
    3.defer与return的先后顺序
    a.定义defer时传入的参数,是作为拷贝传递的。
    也就是说,如果原来的变量值发生变化,不会影响传给defer的参数。
    b.当发生panic时,defer会被执行,但是当调用os.Exit()方法退出程序时,defer并不会被执行。
    c.return是分为两步执行的,第一步赋值给返回值,第二步真正的返回到函数外部。而defer是在第一步之后执行。所以return是包裹着defer运行的
    关键字defer向函数注册退出调用,即主函数退出时,defer后的函数才被调用。defer语句的作用是不管程序是否出现异常,均在函数退出时自动执行相关代码。
    在函数中,程序员经常需要创建资源(比如:数据库连接、文件句柄、锁等) ,为了在函数执行完 毕后,及时的释放资源,Go 的设计者提供 defer (延时机制)。

  • Go 语言参数传递是值传递还是引用传递
    值传递是指在调用函数时将实际参数复制一份传递到函数中,这样在函数中如果对参数进行修改,将不会影响到实际参数。
    引用传递是指在调用函数时将实际参数的地址直接传递到函数中,那么在函数中对参数所进行的修改,将影响到实际参数。
    golang中函数的参数默认为 按值传递,即在函数内部修改传入参数的值是函数外部传入值的 拷贝。
    如果想要使用引用传递,需要将传入的参数设置为 指针类型

  • go make的底层了解吗

  • 分布式ID了解吗
    几乎所有的业务系统,都有生成一个记录标识的ID,这个记录标识往往就是数据库中的唯一主键在这里插入图片描述在这里插入图片描述在这里插入图片描述在这里插入图片描述

  • 内存池了解吗
    go提供的sync.Pool是为了在这里插入图片描述
    对象的复用,如果某些对象的创建比较频繁,就把他们放入Pool中缓存起来以便使用,这样重复利用内存,减少GC的压力

  • nsq中topic和channel有什么区别
    topic:一个可供订阅的话题。
    channel:属于topic的下一级,一个topic可以有多个channel。
    nsq里可以只订阅某一个channel的信息。这样的话,一个topic下无关的channel就不会发过来。
    如果一个channel有多个订阅者,NSQ会使用负载均衡的策略,给其它订阅者发消息

  • goroutine的实现原理
    GMP模型

  • go 分布式raft协议,一致性hash协议

  • map并发安全、slice与array区别,map的原理
    map的并发安全:在这里插入图片描述并发使用map:
    1.一般情况下通过读写锁sync.RWMutex实现对map的并发访问控制,将map和sync.RWMutex封装一下,可以实现对map的安全并发访问。
    2.go1.9之后加入了支持并发安全的Map sync.Map。sync.Map 通过一份只使用原子操作的数据和一份冗余了只读数据的加锁数据实现一定程度上的读写分离,使得大多数读操作和更新操作是原子操作,写入新数据才加锁的方式来提升性能。
    slice和array区别
    在这里插入图片描述
    一个slice由三个部分构成:指针、长度和容量。指针指向第一个slice元素对应的底层数组元素的地址。长度对应slice中元素的数目;长度不能超过容量,容量一般是从slice的开始位置到底层数据的结尾位置。通过len和cap函数分别返回slice的长度和容量。
    在这里插入图片描述

  • 进程间通信有哪些
    1.管道 --概念:在内核中申请一块固定大小的缓冲区,程序拥有写入和读取的权利,一般使用fork函数实现父子进程的通信。
    2,消息队列–在内核中创建一队列,队列中每个元素是一个数据报,不同的进程可以通过句柄去访问这个队列。消息队列提供了⼀个从⼀个进程向另外⼀个进程发送⼀块数据的⽅法。每个数据块都被认为是有⼀个类型,接收者进程接收的数据块可以有不同的类型值
    3,信号量-- 在内核中创建一个信号量集合(本质是个数组),数组的元素(信号量)都是1,使用P操作进行-1,使用V操作+1
    4,共享内存
    概念:将同一块物理内存一块映射到不同的进程的虚拟地址空间中,实现不同进程间对同一资源的共享。 共享内存可以说是最有用的进程间通信方式,也是最快的IPC形式。

  • Golang sync.Map 是怎样实现的
    在这里插入图片描述在这里插入图片描述

通过这种读写分离的设计,解决了并发情况的写入安全,又使读取速度在大部分情况可以接近内建 map,非常适合读多写少的情况。

  • Go适合用来做什么
    在这里插入图片描述

  • go java区别
    在这里插入图片描述

  • Golang的goroutine在什么情况下会让出cpu

  • process 进程和 thread 线程的区别

  • 面试官问消息队列为什么用rabbitmq不用redis
    在这里插入图片描述

  • 之前java和golang的经历,问了golang的interface的区别,继承,gc的原理、区别,双向链表

  • 说了下Nats3.go micro了解多少?
    NATS是一个开源、轻量级、高性能的分布式消息中间件,实现了高可伸缩性和优雅的Publish/Subscribe模型,使用Golang语言开发
    在这里插入图片描述

  • 说了下gin路由,画图模拟

  • silce遇到过哪些坑,原理?append可能导致底层数组改变6.slice作为函数参数怎么解决上面的问题?答return返回,面试官说可以传slice指针

在这里插入图片描述在这里插入图片描述

  • channel实现原理,为什么不用加锁
    channel有个很重要的sutrct,hchan! hchan里面有个 mutex lock
    在这里插入图片描述一个channel主要由一个存放数据的环形队列,一组等待从环形队列中读取数据的而阻塞的goroutine和一组等待从环形队列中写取数据的而阻塞的goroutine,以及并发读写环形队列时的锁构成,这就是传统的生产者消费者模型。在这里插入图片描述

如果用new去创建一个chan就是生成一个上面的结构体的零值,没有任何意义。用make去创建chan时,不但会创建一个上面的结构体,同时也会初始化好背后所需要的各种资源(数据缓存,队列和锁等),并把这些资源同上述结构体中的各项对应关联起来,返回一个有血有肉的chan。

  • . RPC 调用中,客户端都发生了什么,要完成哪些工作?(serialize,connection,parse,我主要说了 serialize 中的 protobuf)2. RPC 客户端如何处理请求失败?(面试官想问客户端如何实现失败重试机制,我引申了 nginx 中的重试方式,提到了幂等性重试)架构相关
  • ,golang如何实现泛型在这里插入图片描述

在这里插入图片描述

  • 内存逃逸分析, 内存逃逸分析(分析了栈帧,讲五种例子,描述堆栈优缺点,点头)
    栈内存分配和释放都非常快,而堆内存需要go的垃圾回收机制来回收处理。如果变量都分配到堆空间中,它需要go频繁的调用垃圾回收机制来进行处理,而gc会占用大量的系统开销,所以通过逃逸分析来把不需要分配到堆内存的变量直接分配到栈上,以减少分配堆内存的开销和不必要的gc调用,提高程序运行效率。
    go在编译阶段确定内存分配到栈上还堆上。
    在go中这个操作是编译器来完成的,编译时通过 go build -gcflags=-m 可以查看逃逸对象
    在栈上申请的内存 :函数返回直接释放,不会引起垃圾回收,对性能没有影响。
    在堆上申请的对象生命周期可以超出函数调用的作用域,需要gc进行回收。
    逃逸分析在编译阶段完成,目的是决定对象分配到栈还是堆

  • map slice 实现(源码分析以及slice内存泄漏分析)

  • slice内存泄漏分析(导致内存暴涨)
    Golang是自带GC的,如果资源一直被占用,是不会被自动释放的,比如下面的代码,如果传入的slice b是很大的,然后引用很小部分给全局量a,那么b未被引用的部分就不会被释放,造成了所谓的内存泄漏。

var a []intfunc test(b []int) {
    
      a = b[:1]  return}

想要理解这个内存泄漏,主要就是理解上面的a = b[:1]是一个引用,其实新、旧slice指向的都是同一片内存地址,那么只要全局量a在,b就不会被回收。

排坑处理:
1.程序正常运行中突然出现内存占用飙升,使用htop命令查看,程序内存占用排第一;使用free -m命令查看,发现可用内存为0;
2.由于程序占用内存过多,导致linux环境响应缓慢卡顿;
3.因为是内存问题,所以首先使用golang的pprof包进行查看heap和alloc内存分配详情。
3.使用命令go tool pprof url就可以获取指定的profile文件,下载到本地,使用命令行进行分析
4.连接上后,可以使用top命令查看内存使用排行
5.然后,使用list命令直接可以查看到具体是哪一行分配了多少内存

  • defer recover 的问题(自己了解不多,简单介绍)
    try catch好处:可以统一针对多个函数调用可能产生的错误做处理
    虽然没有try catch机制,Go其实有一种类似的recover机制在这里插入图片描述defer就类似于面向对象里面的析构函数,在这个函数终止的时候会执行,即使是panic导致的终止。

所以,在cal函数里面每次终止的时候都会检查有没有异常产生,如果产生了我们可以处理,比如说记录日志,这样程序还可以继续执行下去。

Recover()用法是:
恢复用于从紧急情况或错误情况中重新获得对程序的控制。它停止终止序列并恢复正常执行。从延迟函数中调用。它检索通过panic调用传递的错误值。
将Recover()写在defer中,并且在可能发生panic的地方之前,先调用此defer的东西(让系统方法域结束时,有代码要执行。)当程序遇到panic的时候(当然,也可以正常的调用出现的异常情况),系统将跳过后面的代码,进入defer,如果defer函数中recover(),则返回捕获到的panic的值。

  • go优势在这里插入图片描述

go协程怎么切换的 调度大法

在这里插入图片描述在这里插入图片描述

  • 出现阻塞,意味 CPU 在当前执行域没活干了,它在干等,换 go 而言,调度器要 goroutine 上下文切换
  • chan 读写出现阻塞时,runtime 会隐式地进行上下文切换,而在极个别情况下,需要程序员显式编码操作,如下所示
    至于切换到哪个 goroutine,由调度器决定。但可以肯定的是对同一 chan 所相关的 goroutine 执行有序
// 显式交给调度器切换,否则有的goroutine就饿死了
runtime.Gosched()
  • 关键字 select 来完成,它和 switch 控制语句非常相似也被称作通信开关;它的行为像是“你准备好了吗”的轮询机制

网络

  • TCP/IP三次握手在这里插入图片描述在这里插入图片描述
    在这里插入图片描述

  • TCP客户端发送3个数据包,服务器能收到几个

  • socket和消息队列,socket远程通信和本地通信有区别吗
    LocalSocket其通信方式与Socket差不多,只是LocalSocket没有跨越网络边界。

  • http连接复用 http和http2的区别
    Http/1.0每次请求都需要建立新的TCP连接,连接不能复用。Http/1.1新的请求可以在上次建立的tcp连接之上发送,连接可以复用。
    减少重复进行tcp三次握手的开销,提高效率。注意:在同一个tcp连接中,新的请求需要等上次请求收到响应后,才能发送。
    在发送http的请求头中设置Connection: keep-alive
    在这里插入图片描述

  • tcp为什么比udp慢在这里插入图片描述

  • tcp的time_wait作用在这里插入图片描述

  • . https 和 http 的区别是什么?公钥私钥是怎么来的?浏览器如何判断公钥的正确性
    HTTP协议传输的数据都是未加密的,也就是明文的,因此使用HTTP协议传输隐私信息非常不安全,为了保证这些隐私数据能加密传输,于是网景公司设计了SSL(Secure Sockets Layer)协议用于对HTTP协议传输的数据进行加密,从而就诞生了HTTPS。简单来说,HTTPS协议是由SSL+HTTP协议构建的可进行加密传输、身份认证的网络协议,要比http协议安全。

  • tcp如何保证高可靠
    拥塞控制: 当网络拥塞时,减少数据的发送。
    超时重传: 当 TCP 发出一个段后,它启动一个定时器,等待目的端确认收到这个报文段。如果不能及时收到一个确认,将重发这个报文段。
    应用数据被分割成 TCP 认为最适合发送的数据块。

docker k8s

  • k8s原理 listwatch的实现

数据库

  • Mysql事务、存储引擎的区别,优化
  • Mongodb的集群和连接池等,缓存与数据库一致性问题。
  • 数据库的话问主从复制,切换,慢查询优化
  • 数据库,事务的三大范式
  • etcd raft算法
  • 数据库隔离级别
  • redis淘汰策略
  • Redis 问到了 zset 的原理
  • 数据库问到了 B 树和 B+ 树的区别
  • REDIS分布式锁
  • MySQL的innoDB引擎在什么情况下会死锁
  • 事务属性 ACID 都是什么?
  • . 乐观锁和悲观锁的区别?乐观锁怎么判断一致性?
  • 如何防止死锁,可以举例各种语言中都是怎么做的?
  • 哈希表怎么解决冲突(只记得开链解决,顺便提了下redis 哈希表底层实现)

算法

  • 快排、冒泡排序的思想及算法复杂度
  • 二叉树两个节点的公共祖先
  • 判断二叉树是否有排序
  • 21亿个整数,只有两个是重复的,怎么快速找到重复的数
  • 链表怎么检测是否存在环hashmap
  • 二叉树的层序遍历
  • linux日志匹配命令
  • 一个数组,不停的往后面添加数字,O(1)复杂度求数组中位数
  • 对一个整数进行开方,要求精度
  • 二叉树左视图实现一个栈
  • 如果让你做一个url 短网址的项目,你打算怎么做?
  • 反转链表,写了递归非递归两种方法
  • 有 10 万个数,我们要找出最大的 1000 个数,如何做?(因为只要 1000 个数,甚至不关心它们的顺序,可以用比 nlogn 排序更快的方法,因为可以不用对 10 万个数全都排序) 面试官提示,可以用小根堆,取出前 1000 个数组成一个小根堆,后面不断跟 root 比较,小的抛弃,大的插入堆,并替换 root。
  • 二叉树找路径匹配
  • 缓存的LRU淘汰算法,时间复杂度要求O(1)

linux

  • 大牛完全在问linux底层了。虚拟内存、进程调度、缺页、中断

====================================================================================================================================================================================================

golang for循环语句
Go也支持传统的写法for i := 0; i < count; i++ {…}。同样,还有一种for i, a := range aa {…}。这个跟Java相比,除了能够遍历,还能得到当前遍历的索引值

静态,动态类型
在这里插入图片描述在这里插入图片描述

go指针不支持+ -运算

go不能类型继承!! 但是能实现多态!! go不支持方法重载!!

defer panic 执行顺序

  • defer是栈,先进后出的顺序
  • panic会在defer执行完成之后执行在这里插入图片描述

main函数

main函数不能带参数

main函数不能定义返回值

main函数所在的包必须为main包

fallthrough用法

  • Go里面switch默认相当于每个case最后带有break,匹配成功后不会自动向下执行其他case,而是跳出整个switch!!
  • 可以使用fallthrough强制执行后面的case代码在这里插入图片描述

命令的作用
go env: #用于查看go的环境变量

go run: #用于编译并运行go源码文件

go build: #用于编译源码文件、代码包、依赖包

go get: #用于动态获取远程代码包

go install: #用于编译go文件,并将编译结构安装到bin、pkg目录

go clean: #用于清理工作目录,删除编译和安装遗留的目标文件

go version: #用于查看go的版本信息

Printf()、Sprintf()、Fprintf()函数的区别用法是什么?

  • Sprintf(),是把格式字符串输出到指定字符串中,所以参数比printf多一个char*。那就是目标字符串地址。在这里插入图片描述
  • Printf(),是把格式字符串输出到标准输出(一般是屏幕,可以重定向)
  • Fprintf()是格式化输出到一个stream,通常是到文件。所以参数比printf多一个文件指针FILE*。主要用于文件操作

new make区别

  • make只用于内建类型(map、slice 和channel)的内存分配。new用 于各种类型的内存分配。
  • new返回指针
  • make只能创建slice、map和channel,并且返回一个有初始值(非零)的T类型,而不是*T

什么是golang

  • Go语言是Google开发的一种静态强类型、编译型、并发型,并具有垃圾回收功能的编程语言。Go的语法接近C语言,但对于变量的声明有所不同。Go支持垃圾回收功能
  • Go并不包括如枚举、异常处理、继承、泛型、断言、虚函数等功能,但增加了 切片(Slice) 型、并发、管道、垃圾回收、接口等特性的语言级支持

比较两个slice,struct,map是否相等

  • reflect.DeepEqual在这里插入图片描述

linux命令

  • 查看cpu负载,内存占用: top
  • 如何发送信号给一个进程:kill命令

golang slice切片(动态数组)

  • 数组是类型相同的元素的集合

  • var variable_name [SIZE] variable_type 声明数组 var a [3]int 或者 a := [3]int{12, 78, 50}

  • golang 提供简单的遍历数组的方式 for i, v := range a

  • 切片(slice)是建立在数组之上,切片并不存储任何元素而只是对现有数组的引用。切片本身不包含任何数据。它仅仅是底层数组的一个上层表示。对切片进行的任何修改都将反映在底层数组中。

  • 通过 a[start:end] 这样的语法创建了一个从 a[start] 到 a[end -1] 的切片在这里插入图片描述

  • 内置函数 func make([]T, len, cap) []T 可以用来创建切片,该函数接受长度和容量作为参数在这里插入图片描述

  • 切片是动态的,可以使用内置函数 append 添加元素到切片。append(s []T, x …T) []T
    golang数组与切片的区别

  • 在这里插入图片描述

golang Context包作用

  • golang可以很方便创建协程goruntine,但是怎么控制这些协程呢?通过context包,当这个请求超时或者被终止的时候,context优雅地退出所有衍生的 goroutine,并释放资源
  • 流程
    在这里插入图片描述
  • context包函数
  • 在这里插入图片描述
  • Done 要配合 select 语句使用
  • 有两种方法创建根Context:context.Background() context.TODO()根 context 不会被 cancel。这两个方法只能用在最外层代码中,一般使用 Background() 方法创建根 context。
  • 一个 Context 被 cancel,那么它的派生 context 都会收到取消信号(表现为 context.Done() 返回的 channel 收到值)。
    有四种方法派生 context :在这里插入图片描述
    例子,主协程执行cancel()后,子context的Done()收到消息,执行退出
    在这里插入图片描述

select可以用于什么
golang 的 select 就是监听 IO 操作,当 IO 操作发生时,触发相应的动作
每个case语句里必须是一个IO操作,确切的说,应该是一个面向channel的IO操作

golang是强类型弱类型

静态类型:编译时就确定类型,java/C/C++/golang

动态类型:运行时确定 python/PHP

强类型:类型是定义好的,无法改变它的类型了,但是向C语言,虽然定义了一个short,还是可以当成char来用的,因为可以直接操作内存。

弱类型:类型之间可以自由转换

golang 数据竞争检测 DATA RACE

  • Golang中我们使用Channel或者sync.Mutex等锁保护数据,有没golang有一种机制可以检测代码中的数据竞争 go run -race hi.go在这里插入图片描述

golang GC 三色标记清除法

  • Golang的GC算法主要是基于 标记-清扫(markandsweep)算法,并在此基础上做了改进,标记清除法在整个执行时要求长时间stop the world,需要程序暂停,也就是说,这段时间程序会卡在哪儿
  • 通常小对象过多会导致GC三色法消耗过多的GPU。优化思路是,减少对象分配.
  • Golang的三色标记法的大体流程。在这里插入图片描述在这里插入图片描述在这里插入图片描述

在这里插入图片描述在这里插入图片描述

golang中的csp模型
CSP模型是上个世纪七十年代提出的,不同于传统的多线程通过共享内存来通信,CSP讲究的是“以通信的方式来共享内存”。用于描述两个独立的并发实体通过共享的通讯 channel(管道)进行通信的并发模型。 CSP中channel是第一类对象,它不关注发送消息的实体,而关注与发送消息时使用的channel。
’和"区别
''是单个字符 ""里面是字符串

在这里插入图片描述

*和&区别

  • 变量a 在cpu代表一个存储单元,a代表存储单元所存的数据,&a就是该存储单元在cpu 的地址
  • 如果a存储的数据是另一个存储单元的地址.a=&b ,则 *a的值就是另一个存储单元的值, * a=b

定义全局变量只能是 func import type var const
在这里插入图片描述

  • go path go root作用
    GOPATH是我们的工作空间, 用来存放包的目录,可以设置多个,其中,第一个将会是默认的包目录,使用 go get 下载的包都会在第一个path中的src目录下,使用 go install时,在哪个GOPATH中找到了这个包,就会在哪个GOPATH下的bin目录生成可执行文件
    go root其实就是golang 的安装路径

  • golang日志调试方案
    1.采用trace(追踪)、debug(调试)、info(信息)、warn(警告)、error(错误)、fatal(致命)共6种日志级别。
    在这里插入图片描述

  • CAS模式
    https://zhuanlan.zhihu.com/p/56733484
    https://zhuanlan.zhihu.com/p/31122953
    https://zhuanlan.zhihu.com/p/80619752

  • Go语言局部变量分配在栈还是堆?
    go语言编译器会做逃逸分析,分析局部变量的作用域是否逃出了函数的作用域,要是没有就放到栈上,要是超出了函数的作用域就自动放到堆上

  • go 调度模型深入理解
    1.进程和线程和协程:在这里插入图片描述

  • G-Goroutine 的状态
    我们可以将这些不同的状态聚合成三种:等待中、可运行、运行中,运行期间会在这三种状态来回切换
    3.M
    在这里插入图片描述

  • P
    调度器中的处理器 P 是线程和 Goroutine 的中间层,它能提供线程需要的上下文环境,也会负责调度线程上的等待队列,通过处理器 P 的调度,每一个内核线程都能够执行多个 Goroutine,它能在 Goroutine 进行一些 I/O 操作时及时让出计算资源,提高线程的利用率。
    因为调度器在启动时就会创建 GOMAXPROCS 个处理器,所以 Go 语言程序的处理器数量一定会等于 GOMAXPROCS,这些处理器会绑定到不同的内核线程上。
    runtime.p 是处理器的运行时表示,作为调度器的内部实现,它包含的字段也非常多,其中包括与性能追踪、垃圾回收和计时器相关的字段

  • goroutine被阻塞,对应的线程会阻塞吗?

    网络IO阻塞住后不会,原因是标准库中的net包对网络IO做了封装,底层实际基于epoll机制,并不会block线程。但是其他system call就会真的block线程了,比如文件IO,这时候线程会被block, 当前调度器上剩下的协程队列会被转移到新的线程中执行,至于新线程是怎么来的,可能是用的runtime线程池里的空闲线程,也可能是新创建的。
    网络io是利用非阻塞,系统调用会创建新的线程来接管其他goroutine

  • 目前的常用的IO复用模型有三种epoll,poll,select
    1.epoll是一种I/O事件通知机制,是linux 内核实现IO多路复用的一个实现。
    2.什么是io多路复用:I/O多路复用这个模型:一个线程,通过记录I/O流的状态来同时管理多个I/O,可以提高服务器的吞吐能力;IO复用指的是:实现一个线程可以监视多个文件句柄;一旦某个文件句柄就绪,就能够通知应用程序进行相应的读写操作;没有文件句柄就绪时会阻塞应用程序,交出cpu。多路是指网络连接,复用指的是同一个线程
    (1)select==>时间复杂度O(n)
    它仅仅知道了,有I/O事件发生了,却并不知道是哪那几个流(可能有一个,多个,甚至全部),我们只能无差别轮询所有流,找出能读出数据,或者写入数据的流,对他们进行操作。所以select具有O(n)的无差别轮询复杂度,同时处理的流越多,无差别轮询时间就越长。
    (2)poll==>时间复杂度O(n)
    poll本质上和select没有区别, 但是它没有最大连接数的限制,原因是它是基于链表来存储的.
    (3)epoll==>时间复杂度O(1)在这里插入图片描述
    3.epoll与select差别:select版大妈做的是如下的事情:比如同学甲的朋友来了,select版大妈比较笨,她带着朋友挨个房间进行查询谁是同学甲,你等的朋友来了,epoll版大妈就比较先进了,她记下了同学甲的信息,比如说他的房间号,那么等同学甲的朋友到来时,只需要告诉该朋友同学甲在哪个房间即可,不用自己亲自带着人满大楼的找人了
    在这里插入图片描述

  • GC频繁抖动的主要原因
    在这里插入图片描述

  • goroutine底层原理,什么是并发,计算机是怎么实现并发的
    原理:goroutine有三个非常重要的数据结构 GMP…然后调度模型
    并发:两个或两个以上的任务在一段时间内被执行。我们并不关心这些任务是否在同一时刻执行,我们只是知道,这些任务在这一段时间能能够都被执行,当然这一段时间可以很长,也可以很短。
    计算机的分时调用是并发的根本,CPU通过快速的切换作业来执行不同的作业,基本的调度单位在执行的时候可以被阻塞掉,此时就会将CPU资源让出来,等到该调度单位再次被唤醒的时候,又可以使用CPU资源,而操作系统保证了整个的调度过程。

  • chan为什么是安全的?
    看源码就知道channel内部维护了一个互斥锁,来保证线程安全
    channel的设计思想来自于CSP

  • golang的并发模型(CSP):CSP与Actor模型
    两个都是非常复古且外形接近的并发模型
    在这里插入图片描述
    1.Actor:注重的处理单元,也就是Actor,而不是消息传送方式。发送消息时,都需要知道对方是谁(地址),当ActorX要给ActorY发消息时,必须明确知道ActorY的地址。ActorY接收到消息时,就能够知道消息发送者(ActorX)的地址。返回消息给发送者时,只需要按发送者的地址往回传消息就行。
    2.CSP注重的是消息传送方式(channel),不关心发送的人和接收的人是谁。向channel写消息的人,不知道消息的接收者是谁;读消息的人,也不知道消息的写入者是谁。两者比较看来,CSP把发送方和接收方给解耦了。

  • 不加锁实现高并发安全
    无锁是一种乐观的策略,它会假设对资源的访问是没有冲突的。既然没有冲突,自然不需要等待,所以所有的线程都可以在不停顿的状态下持续执行。那遇到冲突怎么办呢?无锁的策略使用一种叫做比较交换的技术(CAS Compare And Swap)来鉴别线程冲突,一旦检测到冲突产生,就重试当前操作直到没有冲突为止。
    CAS算法的过程是这样:它包含三个参数CAS(V,E,N)。V表示要更新的变量,E表示预期值,N表示新值。仅当V值等于E值时,才会将V的值设为N,如果V值和E值不同,则说明已经有其他线程做了更新,则当前线程什么都不做。最后,CAS返回当前V的真实值。CAS操作是抱着乐观的态度进行的,它总是认为自己可以成功完成操作。当多个线程同时使用CAS操作一个变量时,只有一个会胜出,并成功更新,其余均会失败。失败的线程不会被挂起,仅是被告知失败,并且允许再次尝试,当然也允许失败的线程放弃操作。基于这样的原理,CAS操作即使没有锁,也可以发现其他线程对当前线程的干扰,并进行恰当的处理。

Golang提供了了一套原子操作的接口,可以在sync\atomic目录下查看,在里面我们可以看到经典的CAS函数。CAS即比较及交换,比如 CompareAndSwapInt64

  • GMP模型,P是怎么调度G和M的
    当通过go关键字创建一个新的G的时候,它会优先被放入P的本地队列。然后M从P的本地队列里取出一个goroutine并执行。它还有一个 work-stealing(工作窃取)调度算法:当M执行完了当前P的本地队列里的所有G后,它会先尝试从全局队列寻找G来执行,如果全局队列也为空,它就会随机挑选另外一个P,从它的队列里中偷走一半的G到自己的队列中执行。
    工作窃取算法:一个大任务分割为若干个互不依赖的子任务,为了减少线程间的竞争,把这些子任务分别放到不同的队列里,并未每个队列创建一个单独的线程来执行队列里的任务,线程和队列一一对应。比如线程1负责处理1队列里的任务,2线程负责2队列的。但是有的线程会先把自己队列里的任务干完,而其他线程对应的队列里还有任务待处理。干完活的线程与其等着,不如帮其他线程干活,于是它就去其他线程的队列里窃取一个任务来执行。而在这时它们可能会访问同一个队列,所以为了减少窃取任务线程和被窃取任务线程之间的竞争,通常会使用双端队列,被窃取任务线程永远从双端队列的头部拿任务执行,而窃取任务线程永远从双端队列的尾部拿任务执行。
  • golang内存管理优势
    Go语言内置运行时(就是runtime),抛弃了传统的内存分配方式,改为自主管理。这样可以自主地实现更好的内存使用模式,比如内存池、预分配等等。这样,不会每次内存分配都需要进行系统调用。
    Golang运行时的内存分配算法主要源自 Google 为 C 语言开发的TCMalloc算法,全称Thread-Caching Malloc。核心思想就是把内存分为多级管理,从而降低锁的粒度。它将可用的堆内存采用二级分配的方式进行管理:每个线程都会自行维护一个独立的内存池,进行内存分配时优先从该内存池中分配,当内存池不足时才会向全局内存池申请,以避免不同线程对全局内存池的频繁竞争。
    Go在程序启动的时候,会先向操作系统申请一块内存(注意这时还只是一段虚拟的地址空间,并不会真正地分配内存),切成小块后自己进行管理。
  • go map如何实现 顺序读取?
    map本身是无序的,可以维护一个有序的slice,比如把map的key按照顺序存在slice里面,
  • 用Go中Set的实现方式
    对于Set类型的数据结构,其实本质上跟List没什么多大的区别。无非是Set不能含有重复的Item的特性,Set有初始化、Add、Clear、Remove、Contains等操作
    底层可以用map来实现set,map的Key不允许重复,value设置为空结构体
  • 抢占式调度非抢占式调度是什么情况
    1.什么情况下会发生抢占式调度呢?
    最常见的现象是你这个进程运行时间太长了,是时候切换到另一个进程了
    然而操作系统怎么去统计运行时间呢?
    计算机有个时钟的概念,每过一段时间,计算机会通知操作系统,告诉操作系统,又过去了一段时间,你去看看,当前运行的进程运行时间是不是过长了,这个时候操作系统就会去搞这个进程了。
  • 非抢占式,让进程运行直到结束或阻塞的调度方式容易实现 适合专用系统
  • golang控制协程(grouting)数量
    方法一:使用带有缓冲的channel 的特性
    直到缓冲区被填满后,写端才会阻塞。
    缓冲区被读空,读端才会阻塞。
    方法二:使用sync.WaitGroup
    WaitGroup 对象内部有一个计数器,最初从0开始,它有三个方法:Add(), Done(), Wait() 用来控制计数器的数量。
  • 互斥锁,自旋锁应用场景
    某个线程要更改共享数据时,先将其锁定,此时资源的状态为“锁定”,其他线程不能更改;直到该线程释放资源,将资源的状态变成“非锁定”,其他的线程才能再次锁定该资源。互斥锁保证了每次只有一个线程进行写入操作,从而保证了多线程情况下数据的正确性
    互斥锁加锁失败后,线程会释放 CPU ,给其他线程;
    自旋锁加锁失败后,线程会忙等待,直到它拿到锁;
  • go map的底层逻辑,复杂度
    总体来说golang的map是hashmap,是使用数组+链表的形式实现的,使用拉链法消除hash冲突。
    哈希函数(常被称为散列函数)是可以用于将任意大小的数据映射到固定大小值的函数,
    哈希冲突
    哈希函数是将任意大小的数据映射到固定大小值的函数。那么,即使哈希函数设计得足够优秀,几乎每个输入值都能映射为不同的哈希值。但是,当输入数据足够大,大到能超过固定大小值的组合能表达的最大数量数,冲突将不可避免!

比较常用的Has冲突解决方案有链地址法和开放寻址法
golang用的是链地址法 发生冲突,就用单链表链起来
1.我们通过key进行hash运算(可以简单理解为把key转化为一个整形数字)然后对数组的长度取余,得到key存储在数组的哪个下标位置,最后将key和value组装为一个结构体,放入数组下标处

  • golang slice容量和长度的概念
    长度不能超过容量
    slice的长度跟数组的长度是一个概念,即在内存中进行了初始化实际存在的元素的个数;
    slice的容量,如果通过make函数创建slice的时候指定了容量参数,那内存管理器会根据指定的容量的值先划分一块儿内存空间,然后才在其中按长度,存放数组元素,多余的部分处于空闲状态。在这里插入图片描述

  • 协程为什么比线程更轻量级(除了大小)
    1.用户态和内核态的区别

内核态: CPU可以访问内存所有数据, 包括外围设备, 例如硬盘, 网卡. CPU也可以将自己从一个程序切换到另一个程序
用户态: 只能受限的访问内存, 且不允许访问外围设备. 占用CPU的能力被剥夺, CPU资源可以被其他程序获取
2.用户态与内核态的切换?

在这里插入图片描述

3.协程运行在用户态,称之为用户态协程;区别的线程在内核态
在这里插入图片描述在这里插入图片描述4.其他区别
协程,是一种比线程更加轻量级的存在,协程不是被操作系统内核所管理,而完全是由程序所控制,因此,没有线程切换的开销,和多线程比,线程数量越多,协程的性能优势就越明显;

  • 什么叫线程上下文切换
    一个 CPU 在一个时刻只能运行一个线程,当其运行一个线程时,由于时间片耗尽或出现阻塞等情况,CPU 会转去执行另外一个线程.
    并且当前线程的任务可能并没有执行完毕,所以在进行切换时需要保存线程的运行状态,以便下次重新切换回来时,能够继续切换之前的状态运行,这个过程就要涉及到用户态和内核态的切换。
  • golang string 和 []byte区别
    1.string和[]byte,底层都是数组
    2.字符串可以为空,但不能为nil。而且字符串的值是不能改变的。
    str其实是个指针,指向某个数组的首地址,另一个字段是len长度
    3.array是数组的指针,len表示长度,cap表示容量
type stringStruct struct {
    
    
    str unsafe.Pointer
    len int
}
type slice struct {
    
    
	array unsafe.Pointer
	len   int
	cap   int
}
  • golang中 interface(多态的实现方式)
  • waitgroup怎么加定时功能
select {
    
    
case <-time.After(time.Second * 5):
    fmt.Println("我超时了")
case <-ch:
    fmt.Println("我结束了")
}   
  • golang内存分配核心思想
    在这里插入图片描述

  • golang内存池什么时候回收
    可以看到在init的时候注册了一个PoolCleanup函数,他会清除掉sync.Pool中的所有的缓存的对象,这个注册函数会在每次GC的时候运行,所以sync.Pool中的值只在两次GC中间的时段有效。

    func init() {
          
          
        runtime_registerPoolCleanup(poolCleanup)
    }
    

    sync.Pool 的垃圾回收发生在运行时 GC 开始之前,实际上就是将所有的对象置为 nil,等着GC做自动回收。

  • sync.pool内存池作用,底层总结
    1.缓存频繁使用的对象,对象重用的机制,减少gc的压力
    2.Get方法并不会对获取到的对象值做任何的保证,因为放入本地池中的值有可能会在任何时候被删除,但是不通知调用者。放入共享池中的值有可能被其他的goroutine偷走。 所以对象池适合用来存储一些临时,无状态的数据,但不适合用来存储数据库连接的实例,因为存入对象池重的值有可能会在垃圾回收时被删除掉
    3.get()顺序

    尝试从本地P对应的那个本地池中获取一个对象值, 并从本地池冲删除该值。
    如果获取失败,那么从共享池中获取, 并从共享队列中删除该值。
    如果获取失败,那么从其他P的共享池中偷一个过来,并删除共享池中的该值,偷不到回去victim幸存者里面找
    如果仍然失败,那么直接通过New()分配一个返回值,注意这个分配的值不会被放入池中。
    New()返回用户注册的New函数的值,如果用户未注册New,那么返回nil
    
    

4.put()顺序

	如果 private 没有,就放在 private
	如果 private 有了,那么就放到 shared 队列的头部

5.New()函数的作用是当我们从Pool中Get()对象时,如果Pool为空,则先通过New创建一个对象,插入Pool中,然后返回对象。

6.poolLocal里面维护了一个private一个shared,看名字其实就很明显了,private是给自己用的,而shared的是一个队列,可以给别人用的。
victim这个从字面上面也可以知道,幸存者嘛,当进行gc的stw时候,会将local中的对象移到victim中去,也就是说幸存了一次gc

type Pool struct {
    
    
    noCopy noCopy
    local     unsafe.Pointer // local fixed-size per-P pool, actual type is [P]poolLocal
    localSize uintptr        // size of the local array
    victim     unsafe.Pointer // local from previous cycle
    victimSize uintptr        // size of victims array
    New func() interface{
    
    }
}

type poolLocalInternal struct {
    
    
	private interface{
    
    }   // Can be used only by the respective P.
	shared  []interface{
    
    } // Can be used by any P.
	Mutex                 // Protects shared.
}
  • sync,pool内存池victim幸存者的作用
    防止服务抖动。第一轮gc,会把Local的对象交给victim. local内为Nil,整个池子会保留这个对象,如果要get可以从victim来取,第二轮清理,才清理victim

  • golang实现键盘输入
    fmt.Scanf包

  • golang零值的概念
    零值是指当你声明变量(分配内存)并未显式初始化时,始终为你的变量自动设置一个默认初始值的策略
    对于值类型:布尔类型为 false, 数值类型为 0,字符串为 “”,数组和结构会递归初始化其元素或字段,即其初始值取决于元素或字段。
    对于引用类型: 均为 nil,包括指针 pointer,函数 function,接口 interface,切片 slice,管道 channel,映射 map

  • chan的底层结构
    1.有缓冲channel来讲,需要有相应的内存来存储数据,实际上就是一个数组,需要知道数组的地址、容量、元素的大小,以及数组的长度也就是已有元素个数,加上这几个字段后,上面的结构体就变成了这样

    type hchan struct {
        qcount   uint           // 数组长度,即已有元素个数
        dataqsiz uint           // 数组容量,即可容纳元素个数
        buf      unsafe.Pointer // 数组地址
        elemsize uint16         // 元素大小
        ...
    }
    

    2.channel支持交替的读写,有缓冲channel内的缓冲数组会被作为一个“环型”来使用,当下标超过数组容量后会回到第一个位置,所以需要有两个字段记录当前读和写的下标位置.
    3.当读和写操作不能立即完成时,需要能够让当前协程在channel上等待,当条件满足时,要能够立即唤醒等待的协程,所以要有两个等待队列,分别针对读和写

    type hchan struct {
        ...
        recvq    waitq  // 读等待队列
        sendq    waitq  // 写等待队列
        ...
    }
    
  • 对关闭的chan读或者写会怎么样?
    1.向一个已关闭的channel发送元素会引起panic
    2 在发送端关闭Channel,接收端还会继续接收到通道中的元素
    3.3. select…case… 尝试从close的channel接受数据会直接获取该channel类型的空值

  • 分布式系统如何保证数据一致性
    1.比如一个数据源发来请求,但是有两个机器,不知道请求会打在哪台机器上,我们可以选主,即选取一个主节点进行写,从节点定期从主节点读(只有主节点才能写)。如果消息是打在了从节点,则消息转发至主节点,让主节点写。

  • 分布式系统如何保证接口幂等性

  • 解决数据库和缓存不一致的方案
    kafka或者redis 监听mysql的 binlog文件,mysql的master节点如果更新了数据 commit成功之后,会将信息写入在binlog中,slave会监听这个文件,然后从master中读取数据保持同步,redis和kafka中的缓存也可以通过这种方式来实现同步。

  • 扫码登录怎么实现
    1.首先打开网站登录页,向浏览器的服务器发送获取登录二维码的请求。服务器收到请求,随机生成一个uuid,将这个id作为key值存入redis服务器,同时设置一个过期时间,再过期后,用户登录二维码需要进行刷新重新获取。
    2.将这个key值和本公司的验证字符串合在一起,通过二维码生成接口,生成一个二维码的图片。将二维码图片和uuid一起返回给用户浏览器
    3.当用户使用登陆后的微信扫描该二维码的时候,会将这个uid和手机上的微信账号及密码产生的token进行绑定,并上传到服务器。
    4.浏览器拿到二维码和uuid后,会每隔一秒向浏览器发送一次,登录是否成功的请求

  • GOMAXPROCS
    GOMAXPROCS参数为Go程序设置可用的逻辑CPU数目,进而限制了Go程序在用户态可执行的线程数目呢?GOMAXPROCS参数的值默认是1,即Go程序最多只能使用一个逻辑CPU来执行并发操作;从Go 1.5版本开始,GOMAXPROCS参数的默认值是机器全部CPU的数目

  • golang map底层实现
    Go语言中map采用的是哈希查找表,由一个key通过哈希函数得到哈希值,64位系统中就生成一个64bit的哈希值,由这个哈希值将key对应到不同的桶(bucket)中,当有多个哈希映射到相同的的桶中时,使用链表解决哈希冲突。
    go中map使用hash作查找,就是将key作哈希运算,得到一个哈希值,根据哈希值确定key-value落在哪个bucket的哪个cell

  • string转化[]byte优化
    []byte和string两种类型间的转换,[]byte其实就是byte类型的切片
    string对应的底层结构体
    在这里插入图片描述
    可以看到它们内部都有一个指针类型(array或str),指向真实数据。另外还有一个len字段,标识数据的长度.slice多了一个cap字段,表示容量大小.
    slice变量间做赋值操作时,只是修改指针指向,不会拷贝真实数据。string变量间赋值也是同样的道理
    []byte和string相互转换,就需要重新申请内存并拷贝内存了。因为Go语义中,slice的内容是可变的(mutable),而string是不可变的(immutable)。如果他们底部指向同一块数据,那么由于slice可对数据做修改,string就做不到immutable了

    func str2bytes(s string) []byte {
          
          
       x := (*[2]uintptr)(unsafe.Pointer(&s))
       b := [3]uintptr{
          
          x[0], x[1], x[1]}
       return *(*[]byte)(unsafe.Pointer(&b))
    }
    
    func bytes2str(b []byte) string {
          
          
       return *(*string)(unsafe.Pointer(&b))
    }
    

    str2bytes() 首先获得 string 成员的 unsafe.Pointer,并填充到 []byte 结构中,并增加了 Cap 项。而对于 bytes2str() 其实更简单,直接抛弃 Cap 即可。这样可避免大批量的拷贝工作,这个方法就是可以绕过类型检查的 unsafe.Pointer
    unsafe.Pointer进行类型转换一般用于读取结构的私有成员变量或者修改结构的变量,可以突破类型系统的限制,任意读写内存

  • slice扩容需要拷贝新内存吗
    如果扩容之后,还没有触及原数组的容量,切片中的指针指向的位置就还是原数组,如果扩容之后,超过了原数组的容量,Go就会开辟一块新的内存,把原来的值拷贝过来,这种情况丝毫不会影响到原数组。

  • GMP模型深入理解
    1.Processor,它包含了运行goroutine的资源,如果线程想运行goroutine,必须先获取P,P中还包含了可运行的G队列。
    2.线程是运行goroutine的实体,调度器的功能是把可运行的goroutine分配到工作线程上。
    3.P的本地队列:同全局队列类似,存放的也是等待运行的G,存的数量有限,不超过256个
    4.所有的P都在程序启动时创建,并保存在数组中,最多有GOMAXPROCS(可配置)个
    5.M线程想运行任务就得获取P,从P的本地队列获取G

  • 协程什么时候后被抢夺
    对运行时间过长的G(10ms)会被抢夺,P上会有时间字段,记录每一个新的G的运行时间,会有监控线程(main函数的 不参与GMP调度模型)拿这个时间来进行对比,如果时间过长 则被通知让出,runtime通过栈增长来通知

  • 怎么判断接口是不是nil
    一个接口包括动态类型和动态值。 如果一个接口的动态类型和动态值都为空,则这个接口为空的。

  • 用内存池怎么保证数据和库里统一,分布式怎么保证统一
    需要更新数据时,先更新数据库,然后把缓存里对应的数据失效掉(删掉)

  • gin框架相关 c.next()函数
    c.Next()表示挂起,后就执行真实的路由函数,路由函数执行完成之后继续执行后续的代码
    next()跳过当前的中间件,执行下一个中间件,待下一个中间件执行完后再回到当前next位置,直接后面的逻辑

    之前的操作一般用来做验证处理,访问是否允许之类的。
    之后的操作一般是用来做总结处理,比如格式化输出、响应结束时间,响应时长计算之类的。
    
    

    中间件是一个洋葱模型,中心为最终处理请求的 handler
    c.Abort()表示终止,也就是说,执行Abort的时候会停止所有的后面的中间件函数的调用。会终止后面所有的该请求下的函数

  • 什么是服务注册发现
    1.服务注册中心主要是维护各个应用服务的ip+port列表,并保持与各应用服务的通讯,在一定时间间隔内进行心跳检测,如果心跳不能到达则对服务IP列表进行剔除,并同时通知给其它应用服务进行更新。

  • GOLANG适合的场景
    适用:
    1.存储系统,因为golang对所有IO类都有优化,如果磁盘吞吐量比较大,golang不失为一种选择
    不适用的场景:
    1 强业务型,这些用PHP/JAVA更合适
    2.依赖很多的第三方C库的模块,协程对全局变量,锁非常敏感

  • golang对磁盘IO性能的优化
    1.bufio bufio.NewReaderSize()函数可以预分配更大的缓存;默认创建一个大小为4096 byte的缓冲区,此后r.Read(buf)都会从缓冲区中读。而普通io每次读/写操作都会执行系统调用,必然会比bufferIO慢很多。毕竟每次系统调用都会从执行从用户态到内核态的切换。
    ioutil.ReadAll会根据情况自动增大,但每次重新分配都会影响性能

  • struct{}使用
    场景:使用channel传递信号,而不是传递数据时
    原理:没数据需要传递时,传递空struct

  • 向nil的chan读或者写数据会永久被阻塞,死锁
    在这里插入图片描述

  • golang常用的包
    1.math 常见的数学运算
    2.strings 字符串处理
    3.builtin 数组、切片、映射
    4.bufio 缓冲 io

  • go缺点
    1.缺少框架
    2.错误处理
    3.软件包管理

  • 在协程切换时候只需要保存协程的上下文

  • jvm模型 java三大特性

猜你喜欢

转载自blog.csdn.net/weixin_42123182/article/details/107637392
今日推荐