go
go
语言基础
优点:
语法简单(去除了复杂的特性如类继承、复杂的异常处理)、编译快
内置并发支持,⽀持轻量级线程(goroutine)和通信(channel),⾼效并发
内置垃圾回收:这居然也算优点,butc和cpp就没有,原因是gc消耗性能,补充解决办法是智能指针
云原生友好:Go 是云原生时代的宠儿,许多工具(如 Docker、Kubernetes、Prometheus)用 Go 编写。\
缺点:
生态还是没有java成熟,泛型都是1.18才引入的错误处理比较麻烦,之前看到一张梗图就是说凌晨三点各个语言的程序员在干什,go就是在写if err!=nil(笑
使用场景微服务:高并发、低延迟、易部署。
网络编程:Web 服务器、API 网关、代理(如 Nginx 的替代品 Caddy)。
分布式系统:消息队列、分布式存储(如 etcd、TiDB)。
DevOps 工具:容器化、CI/CD 工具(如 Docker、Jenkins 的部分插件)。
高性能计算:实时数据处理、推荐系统。
静态类型语言,但类型推断功能强大,在变量声明时可以省略类型,编译器会自动推断
性能
高性能,低延迟: Go 是一种编译型语言,直接编译为机器码,运行时无需虚拟机,因此启动速度快,执行效率高。 内存占用低,垃圾回收经过优化,适用于高性能、低延迟的场景,如微服务和网络应用。 在基准测试中,Go 的性能通常接近 C/C++,尤其在 I/O 密集型任务中表现优异。
稳定,需预热 Java 运行在 JVM(Java 虚拟机)上,依赖 JIT(即时编译)将字节码转为机器码,进行一系列的初始化操作,包括加载类、验证字节码、分配内存等。虽然启动较慢,但经过预热后性能非常稳定,适合长时间运行的应用。 JVM 的 GC 在高负载场景下可能引入停顿(Stop-the-World),但现代版本(如 Java 17+)的 G1 和 ZGC 已大幅优化。 对于 CPU 密集型任务,Java 的性能稍逊于 Go,但在内存管理和复杂计算中有优势。
开发效率
简洁,快速上手 强制统一的代码风格(通过 gofmt),团队协作时代码一致性高。 但缺乏泛型支持(直到 Go 1.18 引入,后续完善中),有时需要手动实现一些通用逻辑。
功能丰富,学习曲线陡 语法更复杂,面向对象特性丰富(如继承、多态),适合构建大型、模块化的系统。 类型系统强大,支持泛型,静态类型检查严格,减少运行时错误。
并发
Goroutines,简单高效 内置 Goroutines 和 Channels,轻量级线程模型,调度由 Go 运行时管理,开销极低(每个 Goroutine 约 2KB)。 并发模型简单直观,适合高并发场景(如网络服务器、分布式系统)。 无需手动管理线程池,开发体验更好。
线程模型,Loom 改进中 依赖线程模型(基于 OS 线程),每个线程开销较大(默认 1MB 栈空间),需要线程池管理以避免资源耗尽。 Java 提供了丰富的并发工具(如 ExecutorService、ForkJoinPool),但使用复杂。 Java 17 引入的 Project Loom(虚拟线程)大幅改进并发性能,未来可能与 Go 的 Goroutines 竞争。
生态
轻量,标准库强 生态系统较新,第三方库数量不如 Java 多,但质量较高,社区活跃。 常用框架如 Gin、Echo(Web 服务)和 gRPC(RPC 调用),轻量且高效。 倾向于“自力更生”,标准库覆盖大部分需求,依赖管理通过 go mod 简化。
成熟,框架丰富 生态系统成熟,库和框架种类繁多(如 Spring、Hibernate),几乎覆盖所有企业级需求。 Spring 生态功能强大,支持微服务(Spring Boot)、批处理(Spring Batch)等,但引入较多依赖,学习成本高。 Maven 和 Gradle 等构建工具非常成熟,依赖管理完善。
部署
单一二进制,易部署 编译为单一静态二进制文件,无需运行时依赖,部署极其简单(复制二进制即可运行)。 容器化(如 Docker)友好,镜像体积小(几 MB 到几十 MB)。 不需要调优运行时参数,运维成本低。
依赖 JVM,调优复杂 依赖 JVM,部署需要安装 JRE/JDK,或打包为 fat JAR(包含依赖),镜像体积通常较大(百 MB 级)。 JVM 参数调优(如堆内存、GC 策略)复杂,需根据业务场景优化。 支持热部署和动态类加载,适合不停机更新。
适用场景
微服务、云原生:快速开发、高并发、低运维成本
企业级、大型系统:构建复杂、模块化、可长期维护
java是静态语言类型吗?那go是吗?为什么go不用在写代码的时候定义数据类型而java需要
首先定义:在编译期间就知道数据类型的语言。二者都是
go不需要是因为它用:=做了类型推断语法来简化类型声明
java后面也有var关键词可以实现这个功能,但只局限于局部变量\
Go面向对象是怎么实现的?
Go没有类的概念,而是通过结构体(struct)和接口(interface)来实现面向对象的特性:通过接口来定义对象的行为,通过结构体的组合特性来实现对象的组合。
尽管Go语言没有像传统面向对象语言那样的私有成员访问修饰符,但通过首字母大小写来控制成员的可⻅性,实现了封装的效果。首字母大写的成员是公有的,可以被外部包访问;首字母小写的成员是私有的,只能在定义的包内访问。
\
make和new是两个用于分配内存的内建函数, 在使用场景和返回值类型上有明显的区别。
make——创建并初始化 slice、map、channel。它返回的是被初始化的非零值(非nil)的引用类型。
new —— 分配内存但不初始化:用于分配值类型的内存(如结构体、数组),并返回该值类型的指针。它返回的是分配的零值的指针。
思路:数组数据结构、复杂度——切片数据结构——切片扩容机制——qppend导致共享失效——make和new的区别
数组和切片的区别
长度固定,定义时就确定,不能动态改变。
长度可变,底层基于数组实现。
值类型,赋值或传参时,会复制整个数组。
引用类型,赋值或传参时,仍然指向同一块底层数组。
存储在 连续的内存块 中,访问速度快。
由 指针、长度、容量 组成。
不常用,因为长度固定,不够灵活。
更常用,是 Go 语言推荐的数据结构。
个人理解:可以看作动态数组(长度可变)但是本质是数组的引用,扩容时会创建新的数组并复制数据【最后这句话一定要讲啊,不然怎么扯到切片扩容】
切片数据结构
在 Go 中,切片并不是一个独立的数据结构,而是一个封装了底层数组的结构体。它包含三个关键字段:
指针(Data 指针):指向底层数组的起始位置
长度(Len):表示当前切片的元素个数
容量(Cap):表示底层数组从切片起始位置开始最多能容纳的元素个数
\
切片扩容
总的来说——指数增⻓:对于小切片,扩容时增加的容量可能相对较小,避免了内存的过度浪费。而对于大切片,扩容时增加的容量可能较多。 go1.18 之前:
如果期望容量大于当前容量的两倍就会使用期望容量;
如果当前切片的长度小于 1024 就会将容量翻倍;
如果当前切片的长度大于 1024 就会每次增加 25% 的容量,直到新容量大于期望容量(减少内存碎片)
go1.18 之后:优化了切片扩容的策略,让底层数组大小的增长更加平滑
如果期望容量大于当前容量的两倍就会使用期望容量;
如果当前切片的长度小于阈值(默认 256)就会将容量翻倍;
如果当前切片的长度大于等于阈值(默认 256),就会每次增加 25% 的容量,容量基准是 newcap + 3*threshold,直到新容量大于期望容量;
扩容步骤:
申请一个更大的底层数组(一般按
2 倍
规则扩展)。将旧数据复制到新的数组中。
返回新的切片,指向新的底层数组。【切片和数组都是新的,地址都是新的】
很坏的一道分析题
\
数据结构——扩容——特点:无序和不安全——详细解释特点
数据结构
使用哈希表实现键值对的映射:
使用key和随机种子计算哈希值,并且保证均匀分布
再通过位运算定位到具体桶
然后通过tophahs快速筛选
最后键和值按顺序存入桶
操作复杂度都是O(1)\
map扩容
触发条件:
负载因子过高 count / (2^B) > 6.5(经验值,实际算法是13/2,比8小一点)
溢出桶过多——哈希冲突,链地址法
扩容步骤:
Go 使用增量扩容,避免一次性迁移所有数据。
翻倍扩容(桶数量加倍): B 加 1,桶数量从 2^B 变为 2^(B+1)。
等量扩容(桶数量不变): 不增加桶数量,仅重新分配元素,优化分布。
新桶分配后,oldbuckets 指向旧桶,buckets 指向新桶。
每次读写操作时,逐步将旧桶的键值对迁移到新桶(通过 evacuate 函数)。
迁移完成后,释放旧桶。
\
遍历无序性
原因:底层是哈希表,桶的顺序由哈希值决定,而根据随机种子计算哈希值,随机种子都是随机的,相同键的哈希值在不同运行中映射到不同桶。优势就是增强安全性 怎么有序?将 map 的键提取到切片中,排序后按顺序访问。
复杂度:O(n log n)(排序)+ O(n)(遍历)。
\
并发安全性
原因:
底层没加锁:hmap和bmap都没有内置同步机制比如锁,也是go设计者追求性能
并发读写冲突:多 goroutine 同时写 map 时,可能覆盖彼此的修改,或导致桶结构损坏。
如何线程安全?
\
defer 用于延迟函数的执行,它会将函数调用推迟到包含 defer 语句的函数执行完成之后。通常用于资源释放、锁的释放、日志的记录等。 执行顺序:defer语句是按照后进先出(LIFO)的顺序执行的,即最后一个defer语句会最先执行。 函数参数是在哪个时刻确定的:defer语句中的函数参数在 defer 语句被执行时就已经确定了,而不是在函数实际调用时。因此,如果defer语句中有函数参数,这些参数的值是在defer语句执行时就会被计算并保留。
在什么情况下会有问题?:在循环中使用 defer 且 defer 引用循环变量时,因 defer 延迟执行,会出现循环结束后函数执行时用的是最后一次循环变量值的情况。
上述代码输出的结果是 5 个 5 ,而不是 0 到 4 。问题出现在 defer 使用闭包(匿名函数)时,因为闭包捕获的是外部变量的引用,而不是值的副本。避免这种问题的一种方法是在循环体内部创建一个局部变量,将循环变量的值传递给defer中的函数。\
并发
定义
都是并发编程的概念进程:
操作系统分配资源的基本单位
每个进程都有自己的独立内存空间,不同进程之间的数据不能直接共享
进程间的通信(IPC,如管道、共享内存、消息队列)成本较高。
Go程序运行时就是一个 进程。 线程:
操作系统调度的最小执行单位,多个线程共享同一个进程的资源(如内存、文件句柄)。
线程上下文切换比进程更快,但仍然有较高的 内核态 开销(系统调用)。
常见问题:死锁、竞争等等
Go内部维护了一个线程池,不会直接暴露给开发者 协程(Goroutine):
Goroutine 是 Go 运行时管理的轻量级线程,属于 用户态线程,由 Go 运行时调度,而非操作系统。
共享内存,但 Go 提倡 通过 channel 进行通信 来避免并发问题。
创建开销极小,相比线程 切换开销低,没有内核态和用户态的转换。
go func() 创建一个 Goroutine
对比
为什么 Go 选择 Goroutine 而不是直接使用线程?
降低内存开销:Goroutine 的默认栈空间只有 2KB(相比于线程的 1MB)。
减少调度开销:Go 运行时提供 用户态调度,避免 线程级上下文切换 的系统调用开销。
高效并发:Go 使用 M:N 线程模型,能够在 多个 CPU 核心上高效执行。
简化并发编程:使用
channel
进行通信,避免手动加锁的复杂性。
\
协程和线程的区别
协程是 Go 运行时(runtime)提供的一种轻量级并发模型,它必须映射到操作系统的线程Thread上才能真正执行,从而降低系统开销,提高并发能力
操作系统管理线程,线程管理 CPU 资源。
Go 运行时管理 Goroutine,并把它们调度到线程上执行。
==》Go 运行时使用 M:N 模型,在少量 OS 线程上调度大量 Goroutine。\
线程是同步机制,由操作系统调度,多个线程可以并行执行;而协程是异步机制,由程序自己调度。
线程是抢占式,而协程是非抢占式的。需要用户释放使用权切换到其他协程,因此同一时间其实只有一个协程拥有运行权,相当于单线程的能力。
协程能保留上一次调用时的状态。
thread
goroutine
os调度
Go 运行时调度
抢占式调度,时间片,系统强制切换
非抢占式调度,携程自己让出执行权yeild
多线程可以并行,多核CPU
多线程才能并行
挂起后可以保留上次调用状态
存储在线程栈,内核态代价高,被动的——需要加锁保护
主存储在用户态,代价小,动的——避免数据竞争
协程优势:
节省cpu,避免系统内核级的线程频繁切换造成的cpu资源浪费
节约内存
稳定性
开发效率,协程是合作式的,可以方便地将一些操作异步化
既然协程这么好,那大家都去用协程啊?
与特定操作系统功能紧密结合:某些操作系统提供了特定的功能或 API,是基于线程模型设计的。
对线程亲和性有严格要求:有时候应用程序需要将某些任务固定在特定的 CPU 核心上执行,以提高性能或满足特定的实时性要求。操作系统线程可以通过设置线程亲和性(CPU 绑定)来实现
与其他语言的线程库交互
需要精细控制线程的生命周期和状态
并行和并发的区别
定义
Go语言的并发模型建立在goroutine和channel之上。其设计理念是 共享数据通过通信而不是通过共享来实现Go 并发模型 = Goroutine + Channel + 并发控制机制(select
、sync
、atomic
)
Goroutine 是 Go 并发的核心,调度开销极低。程序可以同时运行多个goroutines,它们共享相同的地址空间。
Channel 负责 Goroutine 之间的通信,避免手动加锁的复杂性。
Go 运行时采用 M:N 调度模型,多个 Goroutine 复用少量 OS 线程,提高性能。
工作池、生产者-消费者等模式 使 Go 能轻松实现高并发任务处理。
控制并发行为的辅助机制
多路复用:select 语句允许在多个通道操作中选择一个执行:处理多个通道的并发操作,避免了阻塞。
sync.Mutex
:提供互斥锁(Mutex),保证同一时刻只有一个 Goroutine 访问共享变量。sync.Cond
:提供条件变量(Condition Variable),支持 Goroutine 按条件同步执行。原子操作:
sync/atomic
包包括一系列原子性操作,用于在不使用锁的情况下进行安全的并发操作,比互斥锁更加轻量
\
goroutine
goroutine(协程)是一种轻量级的线程,默认占用 2KB 的栈空间且支持动态扩容(最大可达 1GB)Goroutines 使得程序可以并发执行,而无需显式地创建和管理线程,一个Go 程序可同时运行成千上万个 goroutine。通过关键字 go 可以启动一个新的 goroutine每个 goroutine 都有自己的独立栈空间,数据不互相干扰 Go什么时候发生阻塞?阻塞时调度器会怎么做。
✅ 阻塞发生的几种情况
Channel 读写不匹配
Mutex 互斥锁
系统调用
主 Goroutine 结束
✅ 阻塞时 Go 运行时的调度策略
暂停阻塞的 Goroutine,将其放入等待队列。
调度其他 Goroutine 继续执行,避免 CPU 空闲。
如果所有 Goroutine 阻塞,可能创建新的 OS 线程来继续执行任务。
Go 1.14+ 支持抢占式调度,防止 Goroutine 长时间占用 CPU 资源。
✅ 优化方式
合理使用
select {}
监听多个 Channel,避免 Goroutine 永久阻塞。尽量使用
sync.WaitGroup
代替time.Sleep()
,保证 Goroutine 正确退出。对于 IO 操作,使用
context.WithTimeout()
防止长时间阻塞。
goroutine什么情况会发生内存泄漏?如何避免。
Channel 没有数据,Goroutine 读取时阻塞。——使用
select {}
监听多个通道,避免 Goroutine 永久阻塞。生产者 Goroutine 不断发送数据,但没有消费者,导致 Goroutine 被阻塞。
Goroutine
for range
监听 Channel,但通道未关闭,导致 Goroutine 永远阻塞。——使用close(ch)
让range
自动退出。Goroutine 运行后,主 Goroutine 退出,导致子 Goroutine 存活但无人管理。——使用
context.WithTimeout()
控制 Goroutine 生命周期。/使用sync.WaitGroup
确保 Goroutine 正常退出。
\
可以实现优先级操作吗?
本身是不支持优先级的使用多个队列,按队列顺序执行select+管道\
可以限制并发量吗?
有缓冲的管道
信号量控制数量
GMP(重要)
GMP 指的是 Go 的运行时系统(Runtime)中的三个关键组件:Goroutine、M(Machine)、P(Processor)。
Goroutine:Goroutine 是 Go 语言中轻量级的执行单元,由 Go 运行时管理。它类似于线程,但创建和销毁的开销更小,占用的系统资源也更少。一个 Go 程序中可以同时存在成千上万个 Goroutine。
操作系统线程Machine:M 代表操作系统线程,是由操作系统内核管理的执行单元。每个 M 都对应一个底层的操作系统线程,负责执行具体的任务。
处理器Processor:P 是调度器的上下文,它维护了一个本地的 Goroutine 队列,同时也可以从全局队列中获取 Goroutine。每个 P 都需要绑定一个 M 才能执行 Goroutine。
GMP 模型的工作原理如下:
初始化:创建一些操作系统线程和处理器,每个处理器绑定一个操作系统线程
调度过程:
本地队列调度:每个 P 都有一个本地的 Goroutine 队列,当创建一个新的 Goroutine 时,它会优先被放入当前 P 的本地队列中。M 会从绑定的 P 的本地队列中取出 Goroutine 并执行。
全局队列调度:如果某个 P 的本地队列为空,它会尝试从全局队列中获取 Goroutine。全局队列是所有 P 共享的,用于存储新创建的或被调度器放入的 Goroutine。
工作窃取机制:当某个 P 的本地队列和全局队列都为空时,它会从其他 P 的本地队列中 “窃取” 一半的 Goroutine 到自己的本地队列中执行。这种机制可以保证各个 P 的负载均衡,提高系统的整体性能。
阻塞和唤醒:当一个 Goroutine 发生阻塞(如进行 I/O 操作)时,绑定的 M 会将该 Goroutine 挂起,并从队列中取出另一个 Goroutine 继续执行。如果没有其他可执行的 Goroutine,M 会进入休眠状态。当阻塞的 Goroutine 准备好继续执行时,它会被重新放入某个 P 的本地队列中,等待调度执行。
高性能体现在:
减少线程创建和切换开销
高效的任务调度
Channel
Channel(通道)是用于在goroutines之间进行通信的一种机制。通道提供了一种并发安全的方式来进行goroutines之间的通信。通过通道,可以避免在多个goroutines之间共享内存而引发的竞态条件问题,因为通道的读写是原子性的。
Channel 发送和接收的基本特性
发送和接收操作互斥:同一时刻,对同一通道只能有一个发送或接收操作进行。
发送和接收是原子操作
未完成前,发送和接收会阻塞
用途
数据传递: 主要用于在goroutines之间传递数据,确保数据的安全传递和同步。
同步执行: 通过Channel可以实现在不同goroutines之间的同步执行,确保某个goroutine在另一个goroutine完成某个操作之前等待。
消息传递: 适用于实现发布-订阅模型或通过消息进行事件通知的场景。
多路复用: 使用select语句,可以在多个Channel操作中选择一个非阻塞的执行,实现多路复用。
什么时候会阻塞?
缓冲通道:通道已满/通道为空
非缓冲通道:发送和接受没有配对
未初始化通道\
如何处理阻塞
缓冲通道,在创建通道时指定缓冲区大小,即创建一个缓冲通道。当缓冲区未满时,发送数据不会阻塞。当缓冲区未空时,接收数据不会阻塞。
select语句用于处理多个通道操作,可以用于避免阻塞。
使用time.After创建一个定时器,可以在超时后执行特定的操作,避免永久阻塞。
select语句中使用default分支,可以在所有通道都阻塞的情况下执行非阻塞的操作。
无缓冲的 channel 和有缓冲的 channel ?
对于无缓冲区channel:发送的数据如果没有被接收方接收,那么 发送方阻塞; 如果一直接收不到发送方的数据, 接收方阻塞 ;
有缓冲的channel:发送方在缓冲区满的时候阻塞,接收方不阻塞;接收方在缓冲区为空的时候阻塞,发送方不阻塞。
panic 情况:向已关闭的通道发送数据 → panic
关闭已关闭的通道 → panic
\
互斥锁(mutex)
用于控制对共享资源访问的。确保在任意时刻只有一个线程能够访问共享资源,从而避免了数据竞争和不一致性。
竞态条件: 多个线程同时修改共享资源,导致最终结果依赖于执行时机
数据不一致性: 多个线程同时读写共享资源,导致数据不一致
互斥锁通过在临界区(对共享资源的访问区域)中使用锁来解决这些问题。 mutex有两种模式: normal 和 starvation
正常模式:锁的获取是非公平的,而不是先来先到
饥饿模式:保证等待锁的 Goroutine 按照一定的公平原则获得锁,避免饥饿。
\
垃圾回收机制
Go1.8采用 三色标记法+混合写屏障 。
Go1.3之前:标记清除法
标记(Mark)阶段: 遍历所有 可达对象(从根对象出发)——标记所有 仍然被引用的对象。
清除(Sweep)阶段: 扫描内存,把 未标记的对象释放(即垃圾)
缺点:STW时间过长;每次要遍历整个堆,GC开销高
一开始的做法是将垃圾清理结束时才停止STW,后来优化了方案将清理垃圾放到了STW之后,与程序运行同时进行,这样做减小了STW的时长。
Go1.3之后:三色标记法
黑色(Black):已标记,并且引用的对象也都被标记(不会被回收)。
灰色(Gray):已标记,但它引用的对象还未全部扫描(等待处理)。
白色(White):未标记的对象,GC 认为是不可达的,会被回收。
\
把所有对象标记为白色
将根对象(栈、全局变量)标记为灰色。
遍历灰色对象,把它引用的对象变成灰色,然后自身变成黑色。
重复,直到没有灰色对象,白色对象即垃圾,进行清除。
优势:GC和应用程序同时运行,减少STW时间;避免遍历整个堆缺点:如果程序在标记阶段修改对象引用,可能会导致新对象未被标记,从而错误回收仍然可达的对象;插入写屏障:对象被引用时触发的机制,当白色对象被黑色对象引用时,白色对象被标记为灰色(栈上对象无插入屏障)。删除写屏障:对象被删除时触发的机制。如果灰色对象引用的白色对象被删除时,那么白色对象会被标记为灰色。
一个白色对象被黑色对象引用
灰色对象与它之间的可达关系的白色对象遭到破坏
\
三色标记法+混合写屏障
基于插入写屏障和删除写屏障在结束时需要STW来重新扫描栈,带来性能瓶颈。 混合写屏障 分为以下四步:
GC开始时,将栈上的全部对象标记为黑色(不需要二次扫描,无需STW);
GC期间,任何栈上创建的新对象均为黑色
被删除引用的对象标记为灰色——删除回收
被添加引用的对象标记为灰色——新增追踪
总而言之就是确保黑色对象不能引用白色对象==》不需要STW重新扫描栈,GC停顿时间短。\
STW(初始化) 暂停所有 Goroutine,栈上对象标记为黑色
并发标记(Marking) 恢复goroutine,三色标记法
清除(Sweeping) 扫描所有对象,回收白色对象
分配(Allocation) 分配新对象,决定是否触发 GC。
通过 go tool pprof 和 go tool trace 等工具✅ 使用 sync.Pool
复用对象,减少 GC 触发。 ✅ 限制 Goroutine 数量,防止 Goroutine 泄漏导致 OOM。 ✅ 避免频繁创建大对象,减少 GC 负担。 ✅ 调整 GOGC
,优化 GC 触发频率。
\
内存逃逸指的是变量在函数内部创建,但在函数结束后仍被外部引用,导致变量无法在栈上分配,而必须在堆上分配,从而影响性能。 常见的内存逃逸情况:
返回局部变量的指针
变量被 Goroutine 或 Channel 传递
使用 new 或 make 生成的对象
优化方法:
尽量使用栈上分配(让编译器优化)。
避免不必要的指针返回,减少逃逸。
Web
Gin是一个用于构建Web应用和API的轻量级的Go语言框架,主打极简 API 设计 和 高吞吐量,适合高并发✅ 极简 API:使用 router.Handle()
进行路由管理。 ✅ 高性能:使用 httprouter
进行高效路由匹配,性能比标准 net/http
更优。 ✅ 内置中间件:支持日志、恢复、CORS、JWT 等,简化开发。 ✅ 请求绑定:自动解析 JSON、表单、查询参数等。 ✅ 错误处理:支持链式错误处理,避免 if err != nil
滥用。 ✅ 高并发支持:可轻松处理百万级并发请求,适用于 RESTful API、微服务架构。
Gin 拦截器
请求进入,Gin 按 中间件 → 路由处理函数 → 响应处理 的顺序执行。
中间件使用
c.Next()
让请求继续传递,否则可以提前终止请求。执行顺序:全局中间件 → 路由级中间件 → 处理函数。
ORM(Object-Relational Mapping)框架,适用于 数据库操作(MySQL、PostgreSQL、SQLite)。特点
链式 API 操作,支持 CURD
自动迁移(AutoMigrate)
支持事务、软删除、关联查询
支持 GORM Hook(生命周期钩子)
网站总结
古板网站:https://books.studygolang.com/
go语言设计与实现:https://draveness.me/golang/
个人网站讲解:https://go.cyub.vip/
牛客八股整理:https://www.nowcoder.com/discuss/617667868515229696
怎么讲解参考:https://www.nowcoder.com/discuss/616222020405092352?sourceSSR=users \
面经总结
语言基础
结构体
能进行比较吗
空结构体struct{}{}占用空间吗:不占用,可以用unsafe.Sizeof(struct{}{} 算出为0,通常channel不需要传递数据就会采用空结构体
Defer
多个defer时的执行顺序?(我答了类栈结构,先进后出,说了示例)
defer在函数中执行是在return后还是前?(我答错了,可惜了)
应用场景
interface函数返回了局部变量的指针是安全的吗(安全,编译器有逃逸分析,只说了逃逸分析)make和new的区别,能不能new一下mapGo 语言函数传参是值类型还是引用类型?\
并发
go map
底层
怎么扩容的
为什么会 hash 冲突
go map 并发 panic 如何解决
是不是并发安全的
sync.map的底层
nil map和空map有什么区别
map手动加锁和sync.Map的区别
Channel
底层结构体
在项目中怎么使用的
什么情况下会panic
使用注意事项
什么情况下可以关闭
有缓存和无缓存 channel
什么情况下用有无缓存?
Slice
底层
线程安全?
扩容/不断append,是如何给它分配内存的
数组区别
GMP
p的数量,怎么设置,最高是多少个
本地队列、全局队列的长度,偷取时的偷取数量是多少
GMP模型协程最长的运行时间
如何两个携程实现奇偶数字交替打印
go怎么并发编程下等待多个协程的结束,Add()是什么意思
线程,进程和协程的区别问
GMP的调度是怎么调度的,具体操作(时间轮结构等,完整描述了程序创建和调度的过程,还行)
协程池
golang的锁机制如何解决缓存穿透?用的什么锁?Mutex
还问了go怎么实现高并发,讲一下channel怎么实现并发的go语言是怎么支持并发的(回答了CSP模型,通过goroutine+channel的机制,通过通信而不是共享内存,避免了频繁加锁解锁,同时有一些sync的机制比如waitgroup来主动控制协程的进行。go中还设计了GMP模型来对协程进行调度balabala)
手撕是写生产者消费者问题,最开始用close实现的,然后使用waitgroup\
垃圾回收
goroutine 内存泄漏协程泄露go的内存溢出
6、go内存分配的实现原理(回答的差强人意)
问GC具体的实现,标记流程和对象是什么 (记得没多少,答了个大概,不够)
Gc 算法中怎么实现的可达性分析
三色标记出来之前是怎么去做的,有什么区别\
web
grpc框架和trpc go框架区别?go的casbin包cobra在项目中的应用还了解哪些go框架\
最后更新于