go和其他语言的对比
Golang(也称为Go语言)是一种静态类型、编译型语言,由Google开发,以其简洁、高效和强大的并发处理能力著称。
Golang的设计哲学强调简洁明了。与Python类似,Go语法简洁,易于学习和编写。但与Python等动态类型语言不同,Go是静态类型语言,这意味着在编译时就能捕捉到更多的错误,从而提高代码的稳定性和可维护性。
Golang的一个显著特点是其对并发的原生支持。通过goroutine和channel,Golang使得并发编程变得简单而强大。相比之下,如Java和Python等语言在处理并发时通常需要更复杂的线程和锁机制。
java vs go
参考:https://cloud.tencent.com/developer/article/2118891
面向对象
Java是面向对象的语言,有完整的继承体系,方便的实现多态的机制,能灵活的构造可重用性和易维护性的代码,并且通过OOP能简洁的实现反射机制。这些特性非常符合构造复杂的项目,但由于复杂性同时也导致编码的成本提升。比如Java的反射实现就很简单,他只需要获取类中的信息就可以。
而Go不是面向对象的,它没有传统意义上的继承或反射,构建系统通过组合和嵌入结构体的方式来实现,也就是所说的鸭子类型,多态也是通过接口来实现的,Go 没有类的概念,并且结构体只包含了已声明的字段。因此,我们需要借助“reflection”包来获得所需的信息。由于 Go 中没有结构体的构造函数,所以很多原始类型必须单独处理,并且需要考虑到指针。在 Go 中,我们可以进行指针传递或值传递。Go 的结构体可以将函数作为字段。所有这些都让 Go 的反射变得更加复杂。除此之外,虽然整体上灵活度不如Java,但是它易于编写和维护。
并发性
Go 具有强大的并发模型,其设计基于两级线程模型改进的GMP模型,这样能大大减少并发切换线程的性能开销,而且这些模型统一封装到语言级别的调度层,只需通过关键字 go就可以开启协成,提高了易用性。
Java语言上没有协程的概念,Java的线程模型依然用的内核级线程模型,多线程开发依然需要复杂的实现,而且实现方式有很多种而不用拖着一个像,你需要了解每种实现方式的优缺点才能写出高性能的代码,除了这些还需要了解各种锁,来保障你写的线程是安全的。
垃圾回收
Go 的主要功能之一是垃圾收集,尽管 Java 也有垃圾收集,但它并没有Go的那么快,虽然随着G1和ZGC的出现缩短了与Go的差距,但显然Go更擅长管理内存。它不包含引用链接,而是包含指针,而且Go的垃圾收集器经过大量优化以防止STW。整体上Go的垃圾收集方法更精细。
异常: Go 不使用异常,而是使用错误(error)来表示诸如文件结束之类的事件,并使用运行时恐慌(panic)来表示运行时错误,例如尝试索引数组越界。
泛型: Java的泛型通过类型擦除来实现,使一些代码更清晰,但是不支持泛型数组,并且具有上限和下限的类型通配符等,这让编码变得很复杂。而Go泛型没有Java的复杂,它有一些内置的泛型数据类型,比如,切片和map等,使用起来比较方便清晰易懂。
跨平台性
众所周知,Java是JVM平台的语言,一处编译处处运行,这个是Java引以为傲的优点,但它的运行时环境必须在JVM上,这就导致了Java运行时的臃肿,浪费了一部分资源。
而Go通过编译成可执行文件巧妙的解决了这个问题,虽然它不是跨平台性语言,但它在编码器层面依然可以实现一套代码编译出不同平台的执行文件,可以直接在各个平台上运行,而不用拖着一个像JVM一样的运行时环境。
Go的优点
代码简洁性
静态类型可编译成机器码直接运行
天生多核并行
垃圾收集
跨平台且不依赖运行时环境
简洁的泛型
内置安全性
缺点
有限的库支持
泛型不够完善
灵活度没Java高(这个可算优点也可算缺点)
Java的优点
优秀的文档
优秀的三方库
多线程
灵活性高
平台独立性
完善的语言特性
垃圾回收
JVM
缺点
垃圾回收效果不佳
大量冗余的陈旧实现导致性能不佳
代码的复杂性
复杂的继承机制
c++ vs go
语言简洁性
Go:设计上更简洁和清晰。它消除了包括类和继承在内的许多复杂特性,以简化编程模型。
C/C++:C++特别是,提供了更复杂的特性,如类继承、模板等,这使得语言功能更强大,但也增加了复杂性。
并发编程
Go:内建对并发的支持,通过Goroutines和Channels来简化并发和并行编程。
C/C++:并发编程依赖于线程和锁,通常更复杂且容易出错。
内存管理
Go:自动垃圾收集。
C/C++:手动内存管理,虽然C++11引入了智能指针来简化内存管理。
标准库
Go:拥有一个全面的标准库,尤其在网络编程和并发处理方面表现出色。
C/C++:标准库相对基础,尤其是C语言,需要依赖第三方库进行高级操作。
错误处理
Go:使用返回值来处理错误。
C/C++:C++支持异常处理,C语言依赖于错误码。
编译速度
Go:编译速度快,有利于快速开发。
C/C++:编译速度较慢,尤其是复杂项目。
应用程序二进制大小
Go:生成的二进制文件相对较大。
C/C++:可以生成更小的二进制文件。
python vs go
Python
动态类型语言:Python是动态类型语言,这意味着变量类型在运行时由解释器决定。
语法简洁:Python的语法设计简洁,易于阅读和学习。
广泛的应用领域:Python适用于Web开发、数据分析、人工智能、科学计算、自动化脚本等多种场景。
丰富的第三方库:Python拥有庞大的第三方库生态系统,如Django、Flask、Pandas、NumPy等。
Python的优势
易学易用:Python的语法简洁,适合初学者快速入门。
丰富的库和框架:Python的第三方库和框架丰富,可以快速开发应用。
跨平台:Python支持多种操作系统,开发的应用可以运行在多种平台上。
Python的劣势
运行效率:由于Python是解释型语言,运行效率可能不如编译型语言。
内存管理:Python的内存管理由解释器自动完成,可能不如程序员手动管理内存高效。
Go
静态类型语言:Go是静态类型语言,这意味着变量类型在编译时确定。
并发模型:Go内置了goroutines和channels,使得并发编程变得简单。
编译型语言:Go是编译型语言,编译后的程序运行效率较高。
适用于系统编程:Go适用于构建系统、网络服务器、分布式系统等。
Go的优势
并发性能:Go的并发性能优越,适合开发需要处理大量并发请求的应用。
编译型语言:Go是编译型语言,运行效率高。
跨平台:Go支持多种操作系统,开发的应用可以运行在多种平台上。
Go的劣势
生态系统相对年轻:相比Python,Go的生态系统相对年轻,某些领域的库和框架可能不如Python丰富。
go语言的并发模型
参考自:https://zhuanlan.zhihu.com/p/77206570
在操作系统提供的内核线程之上,Go搭建了一个特有的两级线程模型。goroutine机制实现了M : N的线程模型,goroutine机制是协程(coroutine)的一种实现,golang内置的调度器,可以让多核CPU中每个CPU执行一个协程。
Go语言中支撑整个scheduler实现的主要有4个重要结构,分别是M、G、P、Sched, 前三个定义在runtime.h中,Sched定义在proc.c中。
- Sched结构就是调度器,它维护有存储M和G的队列以及调度器的一些状态信息等。
- M结构是Machine,系统线程,它由操作系统管理的,goroutine就是跑在M之上的;M是一个很大的结构,里面维护小对象内存cache(mcache)、当前执行的goroutine、随机数发生器等等非常多的信息。
- P结构是Processor,处理器,它的主要用途就是用来执行goroutine的,它维护了一个goroutine队列,即runqueue。Processor是让我们从N:1调度到M:N调度的重要部分。
- G是goroutine实现的核心结构,它包含了栈,指令指针,以及其他对调度goroutine很重要的信息,例如其阻塞的channel。
在单核处理器的场景下,所有goroutine运行在同一个M系统线程中,每一个M系统线程维护一个Processor,任何时刻,一个Processor中只有一个goroutine,其他goroutine在runqueue中等待。一个goroutine运行完自己的时间片后,让出上下文,回到runqueue中。 多核处理器的场景下,为了运行goroutines,每个M系统线程会持有一个Processor。
当正在运行的goroutine阻塞的时候,例如进行系统调用,会再创建一个系统线程(M1),当前的M线程放弃了它的Processor,P转到新的线程中去运行。
当其中一个Processor的runqueue为空,没有goroutine可以调度。它会从另外一个上下文偷取一半的goroutine。
系统加入了P,让P去管理G对象,M要想运行G必须先与一个P绑定,然后才能运行该P管理的G。这样带来的好处是,我们可以在P对象中预先申请一些系统资源(本地资源),G需要的时候先向自己的本地P申请(无需锁保护),如果不够用或没有再向全局申请,而且从全局拿的时候会多拿一部分,以供后面高效的使用。
所以可以说Go语言原生支持并发。自己实现的调度器负责将并发任务分配到不同的内核线程上运行,然后内核调度器接管内核线程在CPU上的执行与调度。
陷入内核态具体发生了什么?什么情况下会陷入内核态?为什么要陷入内核态?弊端是什么?如何避免陷入内核态?
1. 陷入内核态具体发生了什么?
当程序从用户态(用户空间)陷入内核态(内核空间)时,CPU 会切换到特权模式,执行内核代码。具体过程如下:
触发机制:通过软中断(如 syscall 指令)、硬件中断(如时钟中断)或异常(如除零错误)。
上下文切换:CPU 保存用户态的执行现场(寄存器、程序计数器等),切换到内核栈。
执行内核代码:内核根据触发原因(如系统调用号)处理请求(如读写文件、分配内存)。
返回用户态:处理完成后,恢复用户态上下文,继续执行用户程序。
2. 什么情况下会陷入内核态?
常见的触发场景包括:
系统调用:用户程序主动请求内核服务(如 open()、write())。
硬件中断:外设事件(如磁盘 I/O 完成、网络包到达)。
异常处理:程序错误(如段错误、缺页异常)。
调度切换:进程/线程切换由内核调度器触发。
资源访问:用户程序尝试执行特权指令(如修改页表)。
3. 为什么要陷入内核态?
特权隔离:用户程序无权直接操作硬件或关键资源(如内存管理、设备驱动),必须通过内核保障安全。
统一抽象:内核提供标准接口(如文件系统、网络协议栈),简化用户程序开发。
系统稳定性:通过内核态集中管理资源,避免用户程序直接竞争或破坏系统状态。
4. 弊端是什么?
**性能开销:**上下文切换、内核/用户态数据拷贝(如 read()/write())会导致延迟。
缓存失效:切换时 CPU 缓存(如 TLB)可能被刷新,降低后续执行效率。
复杂性:频繁陷入内核可能增加代码调试和维护难度。
安全风险:内核代码的漏洞可能被利用(如提权攻击)。
5. 如何避免陷入内核态?
减少系统调用:
合并多次操作(如批量读写)。
使用**内存映射文件(mmap)**替代 read()/write()。
用户态解决方案:
用户态网络协议栈(如 DPDK、XDP)直接处理网络包。
用户态线程库(如协程)减少线程切换开销。
异步 I/O:
使用 epoll、io_uring 等异步接口减少阻塞。
内核旁路(Kernel Bypass):
通过 RDMA、SR-IOV 等技术绕过内核直接访问硬件。
优化内核路径:
使用 eBPF 在内核中运行安全的自定义逻辑,避免上下文切换。
总结
内核态是操作系统安全与资源管理的核心机制,但过度陷入会带来性能损失。通过减少系统调用、使用用户态方案或异步 I/O 等技术,可以在保证安全的前提下提升性能。
go垃圾回收机制?对比java cpp,有什么独特的地方?
写屏障解决什么问题?没有写屏障会导致什么问题?
写屏障 是在垃圾回收过程中使用的一种机制,用来处理并发情况下的新对象创建和对象引用的改变。写屏障可以帮助保持三色标记算法的正确性。
当程序对某个对象的引用发生变更时,写屏障会记录这种变更并确保被引用的对象状态被正确更新(例如,将被引用的白色对象立即变成灰色)。
写屏障确保在垃圾回收进行时,即使有新的对象被创建或对象之间的引用发生变化,垃圾回收器也能够正确地标记这些对象。
如果没有写屏障,垃圾回收器可能无法正确跟踪新修改的指针,这可能导致某些对象被错误地标记为可回收,即使它们仍在使用中。
讲讲你对channel的理解,原理是什么?
chan 是 Go 语言中的一种用于 Goroutine 之间通信的原语,它提供了 Goroutine 之间的同步和数据传递机制。chan 的底层实现涉及队列、锁、信号量以及 Goroutine 的调度等内容。以下是 chan 的底层原理的详细解释:
1. 基本结构
chan 数据结构:在 Go 语言的源码中,chan 的数据结构被定义为一个 hchan 结构体,主要包含以下内容:
buf:缓冲区指针,用于存储通道内的数据(仅当缓冲区大小大于 0 时存在)。
qcount:通道中当前的元素数量。
dataqsiz:缓冲区大小,即通道的容量。
sendx:发送索引,指示下一个值应写入缓冲区的位置。
recvx:接收索引,指示下一个值应从缓冲区中读取的位置。
recvq:一个等待接收数据的 Goroutine 队列(FIFO)。
sendq:一个等待发送数据的 Goroutine 队列(FIFO)。
lock:互斥锁,用于保护 chan 的并发访问。
2. 发送(Send)操作
当一个 Goroutine 执行发送操作时:
检查通道状态:如果通道已关闭,发送操作会引发 panic。
尝试直接发送:如果通道有可用的缓冲区空间,数据直接写入缓冲区。
等待接收:如果通道没有缓冲区空间,且没有等待接收的 Goroutine,当前发送 Goroutine 将被阻塞,并排队等待接收者。
唤醒接收者:如果有等待接收的 Goroutine,发送操作会直接将数据发送给接收者,并唤醒接收者。
3. 接收(Receive)操作
当一个 Goroutine 执行接收操作时:
检查通道状态:如果通道已关闭且缓冲区为空,接收操作会立即返回一个零值和一个标识通道已关闭的标志。
尝试直接接收:如果通道中有数据可供接收,数据直接从缓冲区中读取。
等待发送者:如果缓冲区为空且没有等待发送的 Goroutine,当前接收 Goroutine 将被阻塞,并排队等待发送者。
唤醒发送者:如果有等待发送的 Goroutine,接收操作会直接从发送者那里获取数据,并唤醒发送者。
4. 缓冲通道 vs. 非缓冲通道
非缓冲通道:没有缓冲区,发送和接收操作必须完全同步。发送者必须等待接收者,反之亦然。
缓冲通道:具有缓冲区,发送操作可以在缓冲区未满时立即完成,而无需等待接收者;同样,接收操作可以在缓冲区非空时立即完成,而无需等待发送者。
5. 阻塞与唤醒机制
当一个 Goroutine 在 chan 上被阻塞时,它会被放入相应的 sendq 或 recvq 队列中。
队列是一个 FIFO 队列,确保按照顺序唤醒等待的 Goroutine。
通过 select 语句,可以在多个通道上等待,并以非确定性的方式选择一个可用的通道进行操作。
6. 关闭通道
关闭通道是由 close() 函数完成的。关闭通道后,所有阻塞的接收操作将立即返回一个零值和一个 ok == false 的标志。
关闭通道后,不能再向该通道发送数据,试图发送数据将导致 panic。
7. 性能优化
Go 运行时对 chan 的操作进行了多种优化,例如:
无锁快路径:在没有竞争的情况下,发送和接收操作可以在无锁的快路径上执行,以减少锁争用带来的性能开销。
批量处理:在某些情况下,chan 会尝试批量移动数据以提高性能。
chan 是 Go 语言并发模型的核心部分,它通过通道和 Goroutine 实现了 CSP(Communicating Sequential Processes)模型。理解 chan 的底层实现有助于编写高效并发程序并更好地调试和优化 Go 代码。
多个协程访问同一个map,要加锁吗?为什么channel不用?
需要加锁
数据竞争:如果一个协程正在写入 map,而另一个协程试图读取或写入同一个 map,可能会导致数据结构的破坏,产生不可预测的结果。
不安全的并发:在没有加锁的情况下,Go 的 map 可能会在某些操作中崩溃,抛出运行时错误(如 “concurrent map writes”)。
与 map 不同,Go 的 channel 是设计为在并发环境中安全的,因此不需要显式的加锁。
内置的同步机制:channel 提供了一种内置的同步机制,确保在发送和接收数据时的安全性。
阻塞特性:当一个协程试图从一个空的 channel 中接收数据时,它会被阻塞,直到另一个协程发送数据。同样,当一个协程试图向一个满的 channel 中发送数据时,它也会被阻塞,直到有空间可用。
避免数据竞争:由于 channel 通过阻塞机制保证了对数据的安全访问,开发者无需担心数据竞争的问题。
mysql为什么需要索引?举个例子讲讲根据索引找到记录的过程?
联合索引a,b,c,使用a,c会用到索引吗?
幻读是怎么产生的?为了解决幻读用了什么手段?
redis为什么快?
参考:https://zhuanlan.zhihu.com/p/160157573
讲讲redis主从复制?主从复制之间有差距怎么解决?
讲讲redis哨兵机制?
kafka适合什么场景?为什么适合?
日志收集和分析:Kafka可以作为一个高性能的日志收集和分析平台,接收来自各种系统和应用程序的日志数据,并进行实时处理和分析。
实时数据流处理:Kafka可以处理各种实时数据流,如网站点击流、传感器数据等,并进行实时分析和处理。
消息队列和事件驱动架构:Kafka可以作为一个消息队列或事件驱动架构的核心组件,实现系统之间的解耦和异步通信。
大数据实时处理:Kafka可以与Hadoop等大数据处理工具结合使用,实现实时数据处理和分析。
分布式系统监控和告警:Kafka可以接收来自分布式系统的监控数据和告警信息,并进行实时处理和通知。
手撕 链表插入排序
https://leetcode.cn/problems/insertion-sort-list/description/
/*** Definition for singly-linked list.* type ListNode struct {* Val int* Next *ListNode* }*/
func insertionSortList(head *ListNode) *ListNode {ans := &ListNode{}ans.Next = headq := headfor q!=nil&&q.Next!=nil{qq := ansfor qq!=q &&qq.Next!=nil &&qq.Next.Val<=q.Next.Val{qq = qq.Next}if qq == q{q = q.Nextcontinue}tmp := qq.Nexttmp2 := q.Next.Nextqq.Next = q.Nextqq.Next.Next = tmpq.Next = tmp2}return ans.Next
}