GO面试题集锦

news/2024/5/22 4:43:06/文章来源:https://blog.csdn.net/qq_53267860/article/details/126917032

GO面试题集锦

目录

  • GO面试题集锦
    • slice 扩容机制
    • slice 为什么不是线程安全的
    • map 底层原理
    • map 扩容机制
    • map 遍历为什么无序
    • map 为什么不是线程安全的
    • Map 如何查找
    • Map 冲突解决方式
    • Map 负载因子为什么是 6.5
    • Map 和 Sync.Map 哪个性能好
    • Channel 底层实现原理
    • Channel 有什么特点
    • Channel 为什么是线程安全的
    • Channel 发送和接收什么情况下会死锁?

slice 扩容机制

GO1.17版本及之前
当新切片需要的容量cap大于两倍扩容的容量,则直接按照新切片需要的容量扩容;
当原 slice 容量 < 1024 的时候,新 slice 容量变成原来的 2 倍;
当原 slice 容量 > 1024,进入一个循环,每次容量变成原来的1.25倍,直到大于期望容量。

GO1.18之后
当新切片需要的容量cap大于两倍扩容的容量,则直接按照新切片需要的容量扩容;
当原 slice 容量 < threshold(256) 的时候,新 slice 容量变成原来的 2 倍;
当原 slice 容量 > threshold(256),进入一个循环,每次容量增加(旧容量+3*threshold)/4。

slice 为什么不是线程安全的

slice底层结构并没有使用加锁的方式,不支持并发读写

map 底层原理

map 是一个指针 占用8个字节(64位计算机),指向hmap结构体,hmap包含多个bmap数组() 
type hmap struct { count int  //元素个数,调用len(map)时直接返回 flags uint8  //标志map当前状态,正在删除元素、添加元素..... B uint8  //单元(buckets)的对数 B=5表示能容纳32个元素  B随着map容量增大而变大noverflow uint16  //单元(buckets)溢出数量,如果一个单元能存8个key,此时存储了9个,溢出了,就需要再增加一个单元 hash0 uint32  //哈希种子 buckets unsafe.Pointer //指向单元(buckets)数组,大小为2^B,可以为nil oldbuckets unsafe.Pointer //扩容的时候,buckets长度会是oldbuckets的两倍 nevacute uintptr  //指示扩容进度,小于此buckets迁移完成 extra *mapextra //与gc相关 可选字段 
}type bmap struct { tophash [bucketCnt]uint8 
} 
//实际上编译期间会生成一个新的数据结构  
type bmap struct { topbits [8]uint8     //key hash值前8位 用于快速定位keys的位置keys [8]keytype     //键values [8]valuetype //值pad uintptr overflow uintptr     //指向溢出桶 无符号整形 优化GC
}

map 扩容机制

扩容时机:向 map 插入新 key 的时候,会进行条件检测,符合下面这 2 个条件,就会触发扩容扩容条件:
1.超过负载 map元素个数 > 6.5(负载因子) * 桶个数
2.溢出桶太多
当桶总数<2^15时,如果溢出桶总数>=桶总数,则认为溢出桶过多
当桶总数>2^15时,如果溢出桶总数>=2^15,则认为溢出桶过多扩容机制:
双倍扩容:针对条件1,新建一个buckets数组,新的buckets大小是原来的2倍,然后旧buckets数据搬迁到新的buckets。
等量扩容:针对条件2,并不扩大容量,buckets数量维持不变,重新做一遍类似双倍扩容的搬迁动作,把松散的键值对重新排列一次,使得同一个 bucket 中的 key 排列地更紧密,节省空间,提高 bucket 利用率,进而保证更快的存取。渐进式扩容:
插入修改删除key的时候,都会尝试进行搬迁桶的工作,每次都会检查oldbucket是否nil,如果不是nil则每次搬迁2个桶,蚂蚁搬家一样渐进式扩容

map 遍历为什么无序

map每次遍历,都会从一个随机值序号的桶,再从其中随机的cell开始遍历,并且扩容后,原来桶中的key会落到其他桶中,本身就会造成失序
如果想顺序遍历map,先把key放到切片排序,再按照key的顺序遍历mapvar sl []int
for k := range m {sl = append(sl, k)
}
sort.Ints(sl)
for _,k:= range sl {fmt.Print(m[k])
}

map 为什么不是线程安全的

map设计就不是用来多个协程高并发访问的
多个协程同时对map进行并发读写,程序会panic
如果想线程安全,可以使用sync.RWLock 锁sync.map
这个包里面的map实现了锁,是线程安全的

Map 如何查找

1.写保护机制
先插hmap的标志位flags,如果flags写标志位此时是1,说明其他协程正在写操作,直接panic
2.计算hash值
key经过哈希函数计算后,得到64bit(64位CPU)
10010111 | 101011101010110101010101101010101010 | 10010
3.找到hash对应的桶
上面64位后5(hmap的B值)位定位所存放的桶
如果当前正在扩容中,并且定位到旧桶数据还未完成迁移,则使用旧的桶
4.遍历桶查找
上面64位前8位用来在tophash数组查找快速判断key是否在当前的桶中,如果不在需要去溢出桶查找  
5.返回key对应的指针

Map 冲突解决方式

GO采用链地址法解决冲突,具体就是插入key到map中时,当key定位的桶填满8个元素后,将会创建一个溢出桶,并且将溢出桶插入当前桶的所在链表尾部

Map 负载因子为什么是 6.5

负载因子 = 哈希表存储的元素个数 / 桶个数
Go 官方发现:装载因子越大,填入的元素越多,空间利用率就越高,但发生哈希冲突的几率就变大。装载因子越小,填入的元素越少,冲突发生的几率减小,但空间浪费也会变得更多,而且还会提高扩容操作的次数
Go 官方取了一个相对适中的值,把 Go 中的 map 的负载因子硬编码为 6.5,这就是 6.5 的选择缘由。
这意味着在 Go 语言中,当 map存储的元素个数大于或等于 6.5 * 桶个数 时,就会触发扩容行为。

Map 和 Sync.Map 哪个性能好

type Map struct {mu Mutexread atomic.Valuedirty map[interface()]*entrymisses int
}
对比原始map:
和原始map+RWLock的实现并发的方式相比,减少了加锁对性能的影响。它做了一些优化:可以无锁访问read map,而且会优先操作read map,倘若只操作read map就可以满足要求,那就不用去操作write map(dirty),所以在某些特定场景中它发生锁竞争的频率会远远小于map+RWLock的实现方式
优点:
适合读多写少的场景
缺点:
写多的场景,会导致 read map 缓存失效,需要加锁,冲突变多,性能急剧下降

Channel 底层实现原理

通过var声明或者make函数创建的channel变量是一个存储在函数栈帧上的指针,占用8个字节,指向堆上的hchan结构体
type hchan struct {closed   uint32   // channel是否关闭的标志elemtype *_type   // channel中的元素类型// channel分为无缓冲和有缓冲两种。// 对于有缓冲的channel存储数据,使用了 ring buffer(环形缓冲区) 来缓存写入的数据,本质是循环数组// 为啥是循环数组?普通数组不行吗,普通数组容量固定更适合指定的空间,弹出元素时,普通数组需要全部都前移// 当下标超过数组容量后会回到第一个位置,所以需要有两个字段记录当前读和写的下标位置buf      unsafe.Pointer // 指向底层循环数组的指针(环形缓冲区)qcount   uint           // 循环数组中的元素数量dataqsiz uint           // 循环数组的长度elemsize uint16                 // 元素的大小sendx    uint           // 下一次写下标的位置recvx    uint           // 下一次读下标的位置// 尝试读取channel或向channel写入数据而被阻塞的goroutinerecvq    waitq  // 读等待队列sendq    waitq  // 写等待队列lock mutex //互斥锁,保证读写channel时不存在并发竞争问题
}
等待队列:
双向链表,包含一个头结点和一个尾结点
每个节点是一个sudog结构体变量,记录哪个协程在等待,等待的是哪个channel,等待发送/接收的数据在哪里
type waitq struct {first *sudoglast  *sudog
}type sudog struct {g *gnext *sudogprev *sudogelem unsafe.Pointer c        *hchan ...
}
创建时:
创建时会做一些检查:
-   元素大小不能超过 64K
-   元素的对齐大小不能超过 maxAlign 也就是 8 字节
-   计算出来的内存是否超过限制创建时的策略:
-   如果是无缓冲的 channel,会直接给 hchan 分配内存
-   如果是有缓冲的 channel,并且元素不包含指针,那么会为 hchan 和底层数组分配一段连续的地址
-   如果是有缓冲的 channel,并且元素包含指针,那么会为 hchan 和底层数组分别分配地址
发送时:
-   如果 channel 的读等待队列存在接收者goroutine
-   将数据**直接发送**给第一个等待的 goroutine, **唤醒接收的 goroutine**
-   如果 channel 的读等待队列不存在接收者goroutine
-   如果循环数组buf未满,那么将会把数据发送到循环数组buf的队尾
-   如果循环数组buf已满,这个时候就会走阻塞发送的流程,将当前 goroutine 加入写等待队列,并**挂起等待唤醒**
接收时:
-   如果 channel 的写等待队列存在发送者goroutine
-   如果是无缓冲 channel,**直接**从第一个发送者goroutine那里把数据拷贝给接收变量,**唤醒发送的 goroutine**
-   如果是有缓冲 channel(已满),将循环数组buf的队首元素拷贝给接收变量,将第一个发送者goroutine的数据拷贝到 buf循环数组队尾,**唤醒发送的 goroutine**
-   如果 channel 的写等待队列不存在发送者goroutine
-   如果循环数组buf非空,将循环数组buf的队首元素拷贝给接收变量
-   如果循环数组buf为空,这个时候就会走阻塞接收的流程,将当前 goroutine 加入读等待队列,并**挂起等待唤醒**

Channel 有什么特点

channel有2种类型:无缓冲、有缓冲
channel有3种模式:写操作模式(单向通道)、读操作模式(单向通道)、读写操作模式(双向通道)
写操作模式    make(chan<- int)
读操作模式    make(<-chan int)
读写操作模式    make(chan int)

channel 有 3 种状态:未初始化、正常、关闭

image-20220918115347373

注意点:

一个 channel不能多次关闭,会导致painc
如果多个 goroutine 都监听同一个 channel,那么 channel 上的数据都可能随机被某一个 goroutine 取走进行消费
如果多个 goroutine 监听同一个 channel,如果这个 channel 被关闭,则所有 goroutine 都能收到退出信号

Channel 为什么是线程安全的

不同协程通过channel进行通信,本身的使用场景就是多线程,为了保证数据的一致性,必须实现线程安全
channel的底层实现中,hchan结构体中采用Mutex锁来保证数据读写安全。在对循环数组buf中的数据进行入队和出队操作时,必须先获取互斥锁,才能操作channel数据

Channel 发送和接收什么情况下会死锁?

func deadlock1() {    //无缓冲channel只写不读ch := make(chan int) ch <- 3 //  这里会发生一直阻塞的情况,执行不到下面一句
}
func deadlock2() { //无缓冲channel读在写后面ch := make(chan int)ch <- 3  //  这里会发生一直阻塞的情况,执行不到下面一句num := <-chfmt.Println("num=", num)
}
func deadlock3() { //无缓冲channel读在写后面ch := make(chan int)ch <- 100 //  这里会发生一直阻塞的情况,执行不到下面一句go func() {num := <-chfmt.Println("num=", num)}()time.Sleep(time.Second)
}
func deadlock3() {    //有缓冲channel写入超过缓冲区数量ch := make(chan int, 3)ch <- 3ch <- 4ch <- 5ch <- 6  //  这里会发生一直阻塞的情况
}
func deadlock4() {    //空读ch := make(chan int)// ch := make(chan int, 1)fmt.Println(<-ch)  //  这里会发生一直阻塞的情况
}
func deadlock5() {    //互相等对方造成死锁ch1 := make(chan int)ch2 := make(chan int)go func() {for {select {case num := <-ch1:fmt.Println("num=", num)ch2 <- 100}}}()for {select {case num := <-ch2:fmt.Println("num=", num)ch1 <- 300}}
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.luyixian.cn/news_show_9805.aspx

如若内容造成侵权/违法违规/事实不符,请联系dt猫网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

docker实战教程(七):镜像的分层概念

联合文件系统(UnionFS) 联合文件系统是一种分层、轻量级并且高性能的文件系统,它支持对文件系统的修改作为一次提交来一层层的叠加,同时可以将不同目录挂载到同一个虚拟文件系统下。联合文件系统是docker镜像的基础。镜像可以通过分层来进行继承,基于基础镜像(没有父镜像…

Zookeeper简介

文章目录Zookeeper简介zookeeper能做什么zookeeper的数据模型zookeeper工作机制zookeeper集群的选举机制1、第一次启动选举机制2、非第一次启动选举机制搭建zookeeper的集群Zookeeper简介 zookeeper能做什么 master节点选举&#xff1a;主节点挂了以后&#xff0c;从节点就会…

基于 ANFIS 的非线性回归(Matlab代码实现)

&#x1f4a5;&#x1f4a5;&#x1f4a5;&#x1f49e;&#x1f49e;&#x1f49e;欢迎来到本博客❤️❤️❤️&#x1f4a5;&#x1f4a5;&#x1f4a5; &#x1f468;‍&#x1f393;博主课外兴趣&#xff1a;中西方哲学&#xff0c;送予读者&#xff1a; &#x1f468;‍&a…

字符串函数以及内存函数的模拟实现(超详细,全面理解字符串函数!!!)

目录 一、strlen 1.参数指向的字符串必须要以 \0 结束。 2.注意strlen函数的返回值为size_t&#xff0c;是无符号的 3.模拟实现strlen 二、strcpy 1.源字符串中的 \0 拷贝到目标空间 2.源字符串必须以 \0 结束 3.目标空间必须足够大&#xff0c;以确保能存放源字符串 4…

@Conditional

条件装配:满足Conditional指定的条件,则进行组件注入 @Configuration//告诉springboot这是一个配置类 public class MyConfig {@Bean("tom")public Stu stu01(){return new Stu("汤姆");}@ConditionalOnBean(name="tom")//当容器中有tom组件时…

windows工具:推荐一款可以截长图(滚动截图)的工具FSCapture

windows工具&#xff1a;推荐一款可以截长图&#xff08;滚动截图&#xff09;的工具前言一、FSCapture是什么&#xff1f;二、使用方法1.下载地址和安装2.使用方法前言 有的时候你画的框架图太大&#xff0c;已经超过了一屏&#xff0c;想要导出图片&#xff0c;用普通窗口截…

汇编常用寄存器以及寻址方式

寄存器概览 常用寄存器 AX accumulator 累加寄存器 BX base 基址寄存器 CX count 计数寄存器 DX data 数据寄存器 SP stack pointer 堆栈寄存器 BP base pointer 基址指针寄存器 SI source index 源变址寄存器 DI destination index 目的变址寄存器 IP instruction pointer 指…

ch4 报错修正 Sophus使用

ch4 报错& 修正 &#xff08;1&#xff09; # 添加Eigen头文件 include_directories( "/usr/include/eigen3" )&#xff08;2&#xff09; #include "sophus/so3.hpp" #include "sophus/se3.hpp"&#xff08;3&#xff09; 大量报错但都…

定制qga(作业截图)

文章目录一、qga介绍二、证明qga命令可以正常使用三、创建qga安装包四、总步骤一、qga介绍 qemu guest agent简称qga&#xff0c; 是运行在虚拟机内部的一个守护程序&#xff08;qemu-guest-agent.service&#xff09;&#xff0c; 他可以管理应用程序&#xff0c;执行宿主机发…

声呐直线阵正交混频实验(HEU信息与信号处理创新实践项目一)

写在前面 这个实验原要求是要实现 969696 通道的正交混频变换&#xff08;后来老师说只要不是单通道都行&#xff09;&#xff0c;因此必须使用 FIRFIRFIR IP核&#xff08;手搓FIR一两个通道还行&#xff0c;96通道就太费劲了&#xff09;&#xff0c;所以实验成功的关键就是…

BNU002期-学术沙龙-写好综述

文章目录综述的介绍什么是综述为什么要读综述为什么要写综述怎样写综述综述案例中的问题对于综述写作问题的分类如何避免综述写作问题讨论综述问题框架环节并完善做个升华&#xff1a;谈谈科研和读综述的乐趣本文引用资料的链接补充综述的介绍 本文围绕 什么是综述 我创设这…

微服务基础---认识微服务

1.1认识微服务 1.1.1微服务架构演变 单体架构 将业务的所有功能都集中在一个项目中进行开发&#xff0c;打成一个包部署. 优点&#xff1a;架构简单、部署成本低缺点&#xff1a;耦合度高 分布式架构 根据业务功能对系统进行拆分&#xff0c;每个业务模块作为独立项目开发&am…

软件流程和管理(八):Ethics

目录 1. Ethics 1.1 道德&#xff08;Ethics&#xff09;是什么&#xff1f; 1.2 关于计算机伦理的错误假设 1.3 为什么你要关心建立道德技能和知识 1.4 信息技术的道德责任 1.5 澳大利亚计算机协会的道德准则 1.6 组织中的道德是很重要的 1.7 道德&#xff1a;实用指…

zephyr线程生命周期

ephyr中线程是使用CPU的最小单位&#xff0c;线程从创建后由zephyr内核进行调度&#xff0c;根据运行和等待资源的状况在几个状态中切换&#xff0c;直到线程终止退出生命周期。 线程状态 线程在其生命周期中有下面6种状态&#xff1a; New 创建&#xff1a;线程被创建起来但…

实验2:Open vSwitch虚拟交换机实践

(一)基本要求1.ovs-vsctl基础操作实践:创建OVS交换机,以ovs-xxxxxxxxx命名,其中xxxxxxxxx为本人学号。在创建的交换机上增加端口p0和p1,设置p0的端口号为100,p1的端口号为101,类型均为internal;为了避免网络接口上的地址和本机已有网络地址冲突,需要创建虚拟网络空间…

Redis实现消息队列(双端队列的模式,发布订阅模式)

文章目录 1 采用双端队列的模式1.1 入队出队操作1.2 生产者编写1.3 消费者编写1.4 测试2 采用发布订阅模式2.1 编写生产者2.2 编写消费者2.3 测试​ 本部分,我们使用 redis实现消息队列的功能,采用 redis实现消息队列主要有两种方式:采用 redis自带双端队列实现;采用 r…

【牛客刷题-算法】NC7 买卖股票的最好时机(一)

个人主页&#xff1a;清风莫追 系列专栏&#xff1a;牛客刷题——数据结构与算法 推荐一款面试、刷题神器牛客网&#xff1a;&#x1f449;点击开始刷题学习&#x1f448; 文章目录1.题目描述2.算法设计思路3.代码实现4.运行结果结束语&#xff1a;1.题目描述 描述 假设你有一…

Android移动应用开发之ImageView、ProgressBar和Notification的一些简单使用

文章目录主要文件目录MainActivity:NotificationActivitya.pngic_baseline_account_box_24.xmlactivity_main运行主要文件目录 MainActivity: 这里主要用于按钮响应处理和通知处理 package zufe.scq.hunter;import androidx.appcompat.app.AppCompatActivity; import android…

Letcode动态规划专题-困难

10. 正则表达式匹配 42. 接雨水 1.传统方式-按照行的方式计算 整个思路就是&#xff0c;求第 i 层的水&#xff0c;遍历每个位置&#xff0c;如果当前的高度小于 i&#xff0c;并且两边有高度大于等于 i 的&#xff0c;说明这个地方一定有水&#xff0c;水就可以加 11。 如…

pytest测试框架2【控制用例的执行顺序】

1.pytest加载所有的测试用例都是乱序的,如果想指定用例的顺序,可以使用pytest-ordering插件,指定用例的执行顺序只需要在测试用例的方法前面加上装饰器@pytest.mark.run(order=[num])设置order的对应的num值,它就可以按照num的大小顺序来执行 应用场景:有时运行测试用例需…