【Golang 面试 - 基础题】每日 5 题(八)
✍个人博客:Pandaconda-CSDN博客
📣专栏地址:http://t.csdnimg.cn/UWz06📚专栏简介:在这个专栏中,我将会分享 Golang 面试中常见的面试题给大家~
❤️如果有收获的话,欢迎点赞👍收藏📁,您的支持就是我创作的最大动力💪
36. Go Ma p 遍历为什么是无序的?
使用 range 多次遍历 map 时输出的 key 和 value 的顺序可能不同。这是 Go 语言的设计者们有意为之,旨在提示开发者们,Go 底层实现并不保证 map 遍历顺序稳定,请大家不要依赖 range 遍历结果顺序。
主要原因有 2 点:
-
map 在遍历时,并不是从固定的 0 号 bucket 开始遍历的,每次遍历,都会从一个随机值序号的 bucket,再从其中随机的 cell 开始遍历。
-
map 遍历时,是按序遍历 bucket,同时按需遍历 bucket 中和其 overflow bucket 中的 cell。但是 map 在扩容后,会发生 key 的搬迁,这造成原来落在一个 bucket 中的 key,搬迁后,有可能会落到其他 bucket 中了,从这个角度看,遍历 map 的结果就不可能是按照原来的顺序了。
map 本身是无序的,且遍历时顺序还会被随机化,如果想顺序遍历 map,需要对 map key 先排序,再按照 key 的顺序遍历 map。
func TestMapRange(t *testing.T) {m := map[int]string{1: "a", 2: "b", 3: "c"}t.Log("first range:")for i, v := range m {t.Logf("m[%v]=%v ", i, v)}t.Log("second range:")for i, v := range m {t.Logf("m[%v]=%v ", i, v)}// 实现有序遍历var sl []int// 把 key 单独取出放到切片for k := range m {sl = append(sl, k)}// 排序切片sort.Ints(sl)// 以切片中的 key 顺序遍历 map 就是有序的了for _, k := range sl {t.Log(k, m[k])}
}
37. G o Map 为什么是非线程安全的?
map 默认是并发不安全的,同时对 map 进行并发读写时,程序会 panic,原因如下:
Go 官方在经过了长时间的讨论后,认为 Go map 更应适配典型使用场景(不需要从多个 goroutine 中进行安全访问),而不是为了小部分情况(并发访问),导致大部分程序付出加锁代价(性能),决定了不支持。
场景: 2 个协程同时读和写,以下程序会出现致命错误:fatal error: concurrent map writes
package mainimport ("fmt""time"
)func main() {s := make(map[int]int)for i := 0; i < 100; i++ {go func(i int) {s[i] = i}(i)}for i := 0; i < 100; i++ {go func(i int) {fmt.Printf("map第%d个元素值是%d", i, s[i])}(i)}time.Sleep(1 * time.Second)
}
如果想实现 map 线程安全,有两种方式:
1. 使用读写锁 map + sync.RWMutex
package mainimport ("fmt""sync""time"
)func main() {var lock sync.RWMutexs := make(map[int]int)for i := 0; i < 100; i++ {go func(i int) {lock.Lock()s[i] = ilock.Unlock()}(i)}for i := 0; i < 100; i++ {go func(i int) {lock.RLock()fmt.Printf("map第%d个元素值是%d
", i, s[i])lock.RUnlock()}(i)}time.Sleep(1 * time.Second)
}
2. 使用 Go 提供的 sync.Map
package mainimport ("fmt""sync""time"
)func main() {var m sync.Mapfor i := 0; i < 100; i++ {go func(i int) {m.Store(i, i)}(i)}for i := 0; i < 100; i++ {go func(i int) {v, ok := m.Load(i)fmt.Printf("Load: %v, %v
", v, ok)}(i)}time.Sleep(1 * time.Second)
}
38. G o map 和 sync.Map 谁的性能好,为什么?
Go 语言的 sync.Map 支持并发读写,采取 “空间换时间” 的机制,冗余了两个数据结构,分别是:read 和 dirty。
type Map struct {mu Mutexread atomic.Value // readOnlydirty map[interface{}]*entrymisses int
}
对比原始 map:
和原始 map + RWLock 的实现并发的方式相比,减少了加锁对性能的影响。它做了一些优化:可以无锁访问 read map,而且会优先操作 read map,倘若只操作 read map 就可以满足要求,那就不用去操作 write map (dirty),所以在某些特定场景中它发生锁竞争的频率会远远小于 map + RWLock 的实现方式。
优点:
适合读多写少的场景。
缺点:
写多的场景,会导致 read map 缓存失效,需要加锁,冲突变多,性能急剧下降。
39. 介绍一下 Channel
在 Go 语言中,Channel(通道)是用于多个 Goroutine 之间进行通信的一种机制,通过它们可以安全地传递数据。
Channel 是一种类型,可以使用内置的 make() 函数来创建它们。创建 Channel 时,需要指定它们可以传输的数据类型。
使用 Channel 时,可以在 Goroutine 之间传递数据,通过它们可以进行同步和异步的操作。在使用 Channel 时,需要注意以下几点:
-
Channel 是引用类型,可以像 Slice 和 Map 一样传递给函数。
-
默认情况下,Channel 是无缓冲的,只有当有 Goroutine 准备好接收数据时,发送操作才会成功。如果发送操作没有被接收,发送的 Goroutine 将会阻塞。
-
通过 make() 函数创建带缓冲的 Channel 时,可以指定缓冲区的大小。在缓冲区没有被填满之前,发送操作不会阻塞。
-
Channel 支持多路复用,可以使用 select 语句在多个 Channel 上进行选择和等待。
-
Channel 可以用于控制 Goroutine 的执行,例如通过关闭 Channel 来通知 Goroutine 退出。
使用 Channel 可以帮助解决并发编程中的一些常见问题,例如避免竞态条件、协调不同 Goroutine 之间的操作等。
40. Go channel 的底层实现原理?
概念:
Go 中的 channel 是一个队列,遵循先进先出的原则,负责协程之间的通信(Go 语言提倡不要通过共享内存来通信,而要通过通信来实现内存共享,CSP (Communicating Sequential Process) 并发模型,就是通过 goroutine 和 channel 来实现的)
使用场景:
-
停止信号监听
-
定时任务
-
生产方和消费方解耦
-
控制并发数
底层数据结构:
通过 var 声明或者 make 函数创建的 channel 变量是一个存储在函数栈帧上的指针,占用 8 个字节,指向堆上的 hchan 结构体。
源码包中 src/runtime/chan.go 定义了 hchan 的数据结构:
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.Pointerc *hchan...
}
操作:
创建
使用 make(chan T, cap) 来创建 channel,make 语法会在编译时,转换为 makechan64 和 makechan。
func makechan64(t *chantype, size int64) *hchan {if int64(int(size)) != size {panic(plainError("makechan: size out of range"))}return makechan(t, int(size))
}
创建 channel 有两种,一种是带缓冲的 channel,一种是不带缓冲的 channel。
// 带缓冲
ch := make(chan int, 3)
// 不带缓冲
ch := make(chan int)
创建时会做一些检查:
-
元素大小不能超过 64K。
-
元素的对齐大小不能超过 maxAlign 也就是 8 字节。
-
计算出来的内存是否超过限制。
创建时的策略:
-
如果是无缓冲的 channel,会直接给 hchan 分配内存。
-
如果是有缓冲的 channel,并且元素不包含指针,那么会为 hchan 和底层数组分配一段连续的地址。
-
如果是有缓冲的 channel,并且元素包含指针,那么会为 hchan 和底层数组分别分配地址。
发送
发送操作,编译时转换为 runtime.chansend 函数。
func chansend(c *hchan, ep unsafe.Pointer, block bool, callerpc uintptr) bool
阻塞式:
调用 chansend 函数,并且 block = true。
ch <- 10
非阻塞式:
调用 chansend 函数,并且 block = false。
select {case ch <- 10:...default
}
向 channel 中发送数据时大概分为两大块:检查和数据发送,数据发送流程如下:
-
如果 channel 的读等待队列存在接收者 goroutine
-
将数据直接发送给第一个等待的 goroutine, 唤醒接收的 goroutine。
-
-
如果 channel 的读等待队列不存在接收者 goroutine
-
如果循环数组 buf 未满,那么将会把数据发送到循环数组 buf 的队尾。
-
如果循环数组 buf 已满,这个时候就会走阻塞发送的流程,将当前 goroutine 加入写等待队列,并挂起等待唤醒。
-
接收
发送操作,编译时转换为runtime.chanrecv函数。
func chanrecv(c *hchan, ep unsafe.Pointer, block bool) (selected, received bool)
阻塞式:
调用 chanrecv 函数,并且 block = true。
<ch
v := <ch
v, ok := <ch
// 当channel关闭时,for循环会自动退出,无需主动监测channel是否关闭,可以防止读取已经关闭的channel,造成读到数据为通道所存储的数据类型的零值
for i := range ch {fmt.Println(i)
}
非阻塞式:
调用 chanrecv 函数,并且 block = false。
select {case <-ch:...default
}
向 channel 中接收数据时大概分为两大块,检查和数据发送,而数据接收流程如下:
-
如果 channel 的写等待队列存在发送者 goroutine
-
如果是无缓冲 channel,直接从第一个发送者 goroutine 那里把数据拷贝给接收变量,唤醒发送的 goroutine。
-
如果是有缓冲 channel(已满),将循环数组 buf 的队首元素拷贝给接收变量,将第一个发送者 goroutine 的数据拷贝到 buf 循环数组队尾,唤醒发送的 goroutine。
-
-
如果 channel 的写等待队列不存在发送者 goroutine
-
如果循环数组 buf 非空,将循环数组 buf 的队首元素拷贝给接收变量。
-
如果循环数组 buf 为空,这个时候就会走阻塞接收的流程,将当前 goroutine 加入读等待队列,并挂起等待唤醒。
-
关闭
关闭操作,调用 close 函数,编译时转换为 runtime.closechan 函数。
close(ch)
func closechan(c *hchan)
案例分析:
package main
import ("fmt""time""unsafe"
)
func main() {// ch是长度为4的带缓冲的channel// 初始hchan结构体重的buf为空,sendx和recvx均为0ch := make(chan string, 4)fmt.Println(ch, unsafe.Sizeof(ch))go sendTask(ch)go receiveTask(ch)time.Sleep(1 * time.Second)
}
// G1是发送者
// 当G1向ch里发送数据时,首先会对buf加锁,然后将task存储的数据copy到buf中,然后sendx++,然后释放对buf的锁
func sendTask(ch chan string) {taskList := []string{"this", "is", "a", "demo"}for _, task := range taskList {ch <- task //发送任务到channel}
}
// G2是接收者
// 当G2消费ch的时候,会首先对buf加锁,然后将buf中的数据copy到task变量对应的内存里,然后recvx++,并释放锁
func receiveTask(ch chan string) {for {task := <-ch //接收任务fmt.Println("received", task) //处理任务}
}
总结 hchan 结构体的主要组成部分有四个:
-
用来保存 goroutine 之间传递数据的循环数组:buf
-
用来记录此循环数组当前发送或接收数据的下标值:sendx 和 recvx
-
用于保存向该 chan 发送和从该 chan 接收数据被阻塞的 goroutine 队列: sendq 和 recvq
-
保证 channel 写入和读取数据时线程安全的锁:lock
相关文章:
【Golang 面试 - 基础题】每日 5 题(八)
✍个人博客:Pandaconda-CSDN博客 📣专栏地址:http://t.csdnimg.cn/UWz06 📚专栏简介:在这个专栏中,我将会分享 Golang 面试中常见的面试题给大家~ ❤️如果有收获的话,欢迎点赞👍收藏…...
LeetCode 算法:在排序数组中查找元素的第一个和最后一个位置 c++
原题链接🔗:在排序数组中查找元素的第一个和最后一个位置 难度:中等⭐️⭐️ 题目 给你一个按照非递减顺序排列的整数数组 nums,和一个目标值 target。请你找出给定目标值在数组中的开始位置和结束位置。 如果数组中不存在目标…...
会话存储、本地存储,路由导航守卫、web会话跟踪、JWT生成token、axios请求拦截、响应拦截
1、会话存储、本地存储 前端浏览器中存储用户信息,会话存储、本地存储、cookie 会话存储(sessionStorage):会话期间存储,关闭浏览器后,数据就会销毁 sessionStorage.setItem("account",resp.d…...
strcmp库函数原型
int strcmp(const char *str1, const char *str2) {unsigned const char *s1 (unsigned const char *) str1;unsigned const char *s2 (unsigned const char *) str2;while (*s1 && *s1 *s2) {s1;s2;}return *s1 - *s2; }while (*s1 && *s1 *s2) 一直循环&…...
在 Vue.js 项目中延迟加载子组件
在 Vue.js 中,当父组件渲染时,子组件的生命周期钩子函数会立即执行,即使这些子组件并未显示。这是因为 Vue.js 会在渲染父组件时实例化所有引用的子组件。为了避免不必要的函数执行,我们可以通过使用 v-if 指令和异步组件延迟加载…...
何时会用到设计模式、七大设计原则介绍
以下关于b站尚硅谷相关设计模式视频的总结 设计模式的重要性: 代码重用性(相同的代码,不用编写很多次)、 可读性(编程规范,便于其他程序员阅读和理解)、 可扩展性(增加新功能时&am…...
编程语言发展历史:赋值与相等运算符的变迁历程
本文摘取自笔者书稿《编程语言发展历史》 赋值运算符是编程语言最基础的运算符,其发展历史也非常有趣。最早的赋值语句就是使用等号“”来表示,一些语言为了让赋值运算在数学形式上更加严谨(形如“x x 1”的表达式在数学上不成立࿰…...
求职Leetcode题目(2)
1.柱状图中最大的矩形 据说这是2024年字节二面的题目,我感觉这道题跟接雨水有点类似,最重要的思路还是要找到什么时候能形成矩形的这么个情况,某个范围的矩形的高度,是由最短的柱形来决定的。 我们先整理一下,解决这道…...
深入探索 Postman:使用 API 性能测试优化你的 Web 服务
引言 在当今快速发展的互联网时代,Web 服务的性能至关重要。API 作为服务之间的桥梁,其性能直接影响到整个应用的响应速度和用户体验。Postman,作为一个多功能的 API 开发工具,提供了强大的性能测试功能,帮助开发者评…...
校车购票小程序的设计
管理员账户功能包括:系统首页,个人中心,学生管理,我的乘车信息管理,车辆信息管理,座位管理,系统管理 微信端账号功能包括:系统首页,车辆信息,我的 开发系统…...
拯救数据危机!2024年最受欢迎的数据恢复软件评测
现在大家快速传输资料的方式都变成了电子档,有些数据是存储在电脑上,有些存储在手机,有的存储在U盘甚至其他一些电子设备上。电子设备存储数据方便,丢失数据也总在意料之外。很多时候我们多学会一个工具,比如转转大师数…...
记一次因为在html两个地方引入vue.js导致组件注入失败的问题
这个问题我遇到两次了,是在恼火,不对,三次了,我如果不做这个笔记,我确定我还会遇到第三次。 尾部这个去掉就行 因为头部有了 遇到这种bu g好恼火,解决了又怎么样呢?重蹈覆辙的滋味不好受...
Postman中的智慧重试:API测试用例的错误处理与重试逻辑设置
Postman中的智慧重试:API测试用例的错误处理与重试逻辑设置 在API测试过程中,错误处理和重试逻辑是确保测试准确性和可靠性的重要环节。Postman提供了多种功能来处理测试中可能出现的错误,并允许自定义重试逻辑以适应不同的测试场景。本文将…...
docker部署本地词向量模型
开源项目:GitHub - huggingface/text-embeddings-inference: A blazing fast inference solution for text embeddings models 1. 下载词向量模型 参考我的另一篇博客:langchain 加载本地词向量模型 2. 部署词向量模型 就三行命令 model/data/BAAI/…...
接口自动化中对于文件上传的处理方法
正常的接口自动化基本都是json的格式,对于文件上传是一种特殊的格式是表单格式针对这种表单格式在接口自动化中怎么处理,主要通过工作中使用的一个实际的例子进行分享 举例:web上需要导入一个文件实现相关的功能,主要通过两个接口…...
Java高频面试题分享
文章目录 1. 策略模式怎么控制策略的选取1.1 追问:如果有100种策略呢?1.2 追问:什么情况下初始化Map 2. 什么是索引?什么时候用索引?2.1 追问:怎么判断系统什么时候用量比较少2.2 追问:如何实时…...
kvm虚拟化平台部署
kvm虚拟化平台部署 kvm概念简介 kvm自linux2.6版本以后就整合到内核中,因此可以看做是一个原生架构. kvm虚拟化架构 硬件底层提供物理层面的硬件支持 linux(host),就相当于这个架构中的宿主机,上面运行了多个虚拟机。…...
利用arthas热更新class文件
利用arthas热更新class文件 背景:发现一个bug,家里难以复现,需要在现场环境更新几行代码验证。 arthas-boot version: 3.7.1 java -jar arthas-boot.jar启动arthas 1、利用arthas的sc命令查找确定类名称 sc com.**2、反编译为java文件 …...
天机学堂 第四天 高并发优化总结
前端每隔15秒就发起一次请求,将播放记录写入数据库。 但问题是,提交播放记录的业务太复杂了,其中涉及到大量的数据库操作: 如何进行优化 单机并发能力 变同步为异步 合并写请求 提高单机并发:优化SQL,尽…...
Canva收购Leonardo.ai,增强生成式AI技术能力
每周跟踪AI热点新闻动向和震撼发展 想要探索生成式人工智能的前沿进展吗?订阅我们的简报,深入解析最新的技术突破、实际应用案例和未来的趋势。与全球数同行一同,从行业内部的深度分析和实用指南中受益。不要错过这个机会,成为AI领…...
python政府集中采购管理系统设计与实现
目录同行可拿货,招校园代理 ,本人源头供货商项目背景核心功能模块技术实现要点应用价值项目技术支持获取博主联系方式 源码获取详细视频演示 :同行可合作点击我获取源码->获取博主联系方式->进我个人主页-->同行可拿货,招校园代理 ,本人源头供货商 项目背…...
2026 大模型企业画像梳理技术解析:混乱画像规范方法深度测评
引言随着 AI 搜索成为商业信息获取的主要渠道,大模型生成的企业画像准确性直接影响企业品牌形象和获客效果。据中国 GEO 行业协会 2026 年调研数据显示,超过 76% 的企业反映大模型生成的企业画像存在信息混乱、错误遗漏、业务不匹配等问题,其…...
linux学习笔记之linux文件管理
#文件系统及Shell的基本概念#文件及目录操作命令#VI的使用#软件包的管理一、文件系统及Shell的基本概念 1.文件系统的含义文件系统是用来管理和组织保存在磁盘驱动器上数据的系统软件 2.Linux的文件系统Linux系统采用虚拟文件系统技术(VFS&am…...
告别手动启动:在Windows Server上把Gitblit配置成稳定可靠的后台服务
Windows Server生产环境Gitblit服务化部署全指南 在团队协作开发中,代码仓库的稳定性和可靠性直接影响着整个研发流程的效率。对于使用Windows Server作为基础架构的企业来说,将Gitblit从简单的命令行工具转变为系统服务,是实现7x24小时不间断…...
Unity中大型项目架构选型:GameFramework与QFramework实战对比
1. 为什么这两个框架值得你花时间搞懂——不是“又一个Unity插件”,而是项目基建的分水岭 在Unity中写过三个以上正式项目的人都会遇到同一个临界点:当功能模块超过20个、脚本数量突破500、团队从1人扩展到5人时,原本“拖拽组件写MonoBehavi…...
AirPodsDesktop:在Windows上解锁苹果耳机的完整体验
AirPodsDesktop:在Windows上解锁苹果耳机的完整体验 【免费下载链接】AirPodsDesktop ☄️ AirPods desktop user experience enhancement program, for Windows and Linux (WIP) 项目地址: https://gitcode.com/gh_mirrors/ai/AirPodsDesktop 你是否曾经在W…...
嘉立创EDA专业版安装避坑指南:从下载到第一个ESP32项目实战
嘉立创EDA专业版安装避坑指南:从下载到第一个ESP32项目实战 第一次打开嘉立创EDA专业版时,那个深蓝色界面让我想起了学生时代第一次接触电路设计的场景。作为国产EDA工具的后起之秀,它用更符合国人习惯的操作逻辑和实惠的打板政策,…...
AI辅助编程:发展现状、效率评估与未来展望
引言:AI如何重塑编程范式? 在过去的几年里,人工智能(AI)正以前所未有的速度渗透到软件开发的各个角落。从最初的代码补全工具,到如今能够理解复杂需求、生成完整函数甚至设计系统架构的智能体,AI辅助编程已经从科幻概念演变为开发者日常工作中不可或缺的“副驾驶”。它…...
WarcraftHelper:5分钟解决魔兽争霸III现代兼容性问题的终极指南
WarcraftHelper:5分钟解决魔兽争霸III现代兼容性问题的终极指南 【免费下载链接】WarcraftHelper Warcraft III Helper , support 1.20e, 1.24e, 1.26a, 1.27a, 1.27b 项目地址: https://gitcode.com/gh_mirrors/wa/WarcraftHelper 还在为经典魔兽争霸III在W…...
鸿蒙同城兴趣圈页面构建:今晚活动与同频推荐模块详解
鸿蒙同城兴趣圈页面构建:今晚活动与同频推荐模块详解 前言 在 HarmonyOS 6.0 应用开发中,社交类页面的活动展示和用户推荐是提升用户参与度的核心功能模块。本文将以“同城兴趣圈”应用中的“今晚活动”时间线模块和“同频推荐”用户卡片网格为例&#x…...
