2024年Go最新最新golang语言面试题总结(二)_person{28},2024年最新字节跳动架构师讲解Golang开发

img
img
img

既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,涵盖了95%以上Go语言开发知识点,真正体系化!

由于文件比较多,这里只是将部分目录截图出来,全套包含大厂面经、学习笔记、源码讲义、实战项目、大纲路线、讲解视频,并且后续会持续更新

如果你需要这些资料,可以戳这里获取

1、总线加锁:因为CPU和其他硬件的通信都是通过总线控制的,所以可以通过在总线加LOCK#锁的方式实现原子操作,但这样会阻塞其他硬件对CPU的访问,开销比较大。

2、缓存锁定:频繁使用的内存会被处理器放进高速缓存中,那么原子操作就可以直接在处理器的高速缓存中进行而不需要使用总线锁,主要依靠缓存一致性来保证其原子性下面一个例子使用CAS来实现计数器


var counter int32 = 0

func main() {
	wg := sync.WaitGroup{}
	wg.Add(2)
	go func() {
		defer wg.Done()
		for i := 0; i < 10; i++ {
			atomic.AddInt32(&counter, 1)
		}
	}()
	go func() {
		defer wg.Done()
		for i := 0; i < 10; i++ {
			atomic.AddInt32(&counter, 1)
		}
	}()
	wg.Wait()
	fmt.Println(counter)
}
结果:20

CAS的缺陷
1.循环开销大
可以看到,方法内部用不断循环的方式实现修改。如果CAS长时间一直不成功,可能会给CPU带来很大的开销。

2.只能保证一个共享变量的原子操作
需要对多个共享变量操作时,循环CAS就无法保证操作的原子性。
解决方法:可以把多个变量放在一个对象里来进行CAS操作。

3.ABA问题
CAS需要在操作值的时候,检查值有没有发生变化,如果没有发生变化则更新,但是如果一个值原来是A,变成了B,又变成了A,那么CAS进行检查的时候发现它的值没有发生变化,但是实质上它已经发生了改变 。可能会造成数据的缺失

32、go中被多次问到的GC

什么是GC

GC
堆内存上分配的数据对象,不会再使用时,不会自动释放内存,就变成垃圾,在程序的运行过程中,如果不能及时清理,会导致越来越多的内存空间被浪费,导致系统性能下降。

因此需要内存回收,内存回收分为两种方式

1.手动释放占用的内存空间

程序代码中也可以使用runtime.GC()来手动触发GC。这主要用于GC性能测试和统计

2.自动内存回收

(一)内存分配量达到阀值触发GC
每次内存分配时都会检查当前内存分配量是否已达到阀值,如果达到阀值则立即启动GC。
阀值 = 上次GC内存分配量 * 内存增长率
内存增长率由环境变量GOGC控制,默认为100,即每当内存扩大一倍时启动GC。

(二)定期触发GC
默认情况下,最长2分钟触发一次GC,这个间隔在src/runtime/proc.go:forcegcperiod变量中被声明:

// forcegcperiod is the maximum time in nanoseconds between garbage
// collections. If we go this long without a garbage collection, one
// is forced to run.
//
// This is a variable for testing purposes. It normally doesn't change.
var forcegcperiod int64 = 2 * 60 * 1e9

首先记住三种:

go 1.3 之前采用标记清除法,需要STW(stop the world)需要暂停用户所有操作
go 1.5 采用三色标记法,插入写屏障机制(只在堆内存中生效),最后仍需对栈内存进行STW
go 1.8 采用混合写屏障机制,屏障限制只在堆内存中生效。避免了最后节点对栈进行STW的问题,提升了GC效率

第一个阶段
STW:stop the word,指程序执行过程中,中断暂停程序逻辑,专门去进行垃圾回收。

标记清除法
把根数据段上的数据作为root,基于他们进行进一步的追踪,追踪到的数据就进行标记,最后把没有标记的对象当作垃圾进行释放。

开启STW,
从根节点出发,标记所有可达对象
停止STW,然后回收所有未标记的对象。

第二个阶段

三色标记法

其实实际应用场景是没有三色这个概念,这只是为了人们方便理解这种,抽象出来的一种说法而已,这里的三色对应的就是垃圾回收的三种状态

1、白色:初始状态下所有的对象都是白色,gcmarkBits对应的位为0(该对象会被清理)
2、灰色:对象被进行标记,但是这个对象的子对象(也就是它引用的对象)未被进行标记
3、黑色:对象被标记同时这个对象引用的子对象也被进行标记gcmarkBits对应的位为1(该对象不会被清理)

始状态下所有对象都是白色的。
接着开始扫描根对象a、b:

第三阶段

三色标记——混合屏障

因为go支持并行GC, GC的扫描和go代码可以同时运行, 这样带来的问题是GC扫描的过程中go代码有可能改变了对象的依赖树。

例如开始扫描时发现根对象A和B, B拥有C的指针。

GC先扫描A,A放入黑色
B把C的指针交给A
GC再扫描B,B放入黑色
C在白色,会回收;但是A其实引用了C。
为了避免这个问题, go在GC的标记阶段会启用写屏障(Write Barrier).

启用了写屏障(Write Barrier)后,在GC第三轮rescan阶段,根据写屏障标记将C放入灰色,防止C丢失。

33、说一下乐观锁和悲观锁在go中如何使用

乐观锁:乐观锁是一种乐观思想,即认为读多写少,遇到并发写的可能性低,每次去拿数据的时候都认为别人不会修改,所以不会上锁,但是在更新的时候会判断一下在此期间别人有没有去更新这个数据,采取在写时先读出当前版本号,然后加锁操作(比较跟上一次的版本号,如果一样则更新),如果失败则要重复读-比较-写的操作

var Rlock sync.RWMutex

func Read(wg *sync.WaitGroup) {
	defer wg.Done()
	Rlock.RLock()
	fmt.Println("开始读取")
	time.Sleep(time.Second)
	fmt.Println("读取成功")
	Rlock.RUnlock()
}
func main1() {

	var wg = sync.WaitGroup{}
	wg.Add(6)
	for i := 0; i < 5; i++ {
		go Read1(&wg)
	}
	go Write1(&wg)
	wg.Wait()
}

**悲观锁:**悲观锁是就是悲观思想,即认为写多,遇到并发写的可能性高,每次去拿数据的时候都认为别人会修改,所以每次在读写数据的时候都会上锁,这样别人想读写这个数据就会block直到拿到锁。

func Write1(wg *sync.WaitGroup) {
	defer wg.Done()
	Rlock.Lock()
	fmt.Println("开始写")
	time.Sleep(time.Second * 10)
	fmt.Println("写入成功")
	Rlock.Unlock()
}

func main1() {

	var wg = sync.WaitGroup{}
	wg.Add(6)
	for i := 0; i < 5; i++ {
		go Read1(&wg)
	}
	go Write1(&wg)
	wg.Wait()
}

自旋锁
自旋锁原理非常简单,如果持有锁的线程能在很短时间内释放锁资源,那么那些等待竞争锁的线程就不需要做内核态和用户态之间的切换进入阻塞挂起状态,它们只需要等一等(自旋),等持有锁的线程释放锁后即可立即获取锁,这样就避免用户线程和内核的切换的消耗。线程自旋是需要消耗 cup 的,说白了就是让 cup 在做无用功,如果一直获取不到锁,那线程也不能一直占用 cup 自旋做无用功,所以需要设定一个自旋等待的最大时间。
如果持有锁的线程执行的时间超过自旋等待的最大时间扔没有释放锁,就会导致其它争用锁的线程在最大等待时间内还是获取不到锁,这时争用线程会停止自旋进入阻塞状态。

34、面试遇到的题总结

1、下面代码,有几次错误
func main() {
	var x string = nil
	if x == nil {
		x = "default"
	}
	fmt.Println(x)
}

答:两次 var x string = nil和 if x == nil 

2、下面代码段输出什么?

type Person struct {
    age int
}

func main() {
    person := &Person{28}

    // 1. 
    defer fmt.Println(person.age)

    // 2.
    defer func(p *Person) {
        fmt.Println(p.age)
    }(person)  

    // 3.
    defer func() {
        fmt.Println(person.age)
    }()

    person.age = 29
}
答:29 29 28

3、下面这段代码输出什么?为什么?
type People interface {
    Speak(string) string
}

type Student struct{}

func (stu *Student) Speak(think string) (talk string) {
    if think == "speak" {
        talk = "speak"
    } else {
        talk = "hi"
    }
    return
}

func main() {
    var peo People = Student{}
    think := "speak"
    fmt.Println(peo.Speak(think))
}
答:编译错误 Student does not implement People (Speak method has pointer receiver),值类型 Student 没有实现接口的 Speak() 方法,而是指针类型 *Student 实现该方法。

4、下面输出什么?
type Student struct {
	Name string
}

func main() {
	fmt.Println(&Student{Name: "xxx"} == &Student{Name: "xxx"})
	fmt.Println(Student{Name: "xxx"} == Student{Name: "xxx"})
}
答:指针是取地址
false
true
5、下面代码会报错吗?
func main() {
	fmt.Println([...]string{"1"} == [...]string{"1"})
	fmt.Println([]string{"1"} == []string{"1"})
}
答:数组只能与相同纬度⻓度以及类型的其他数组⽐较,切⽚之间不能直接⽐较。

35、面试中被问到WebSocket与Socket、TCP、HTTP的关系及区别

1.什么是WebSocket及原理

WebSocket是HTML5中新协议、新API。 WebSocket从满足基于Web的日益增长的实时通信需求应运而生,解决了客户端发起多个Http请求到服务器资源浏览器必须要在经过长时间的轮询问题,实现里多路复用,是全双工、双向、单套接字连接,在WebSocket协议下服务器和客户端可以同时发送信息。

原理:

WebSocket 同 HTTP 一样也是应用层的协议,但是它是一种双向通信协议,是建立在 TCP 之上的。

2.理解各种协议和通信层、套接字的含义

IP:网络层协议;(高速公路)

TCP和UDP:传输层协议;(卡车)

HTTP:应用层协议;(货物)。HTTP(超文本传输协议)是建立在TCP协议之上的一种应用。HTTP连接最显著的特点是客户端发送的每次请求都需要服务器回送响应,在请求结束后,会主动释放连接。从建立连接到关闭连接的过程称为“一次连接”。

SOCKET:套接字,TCP/IP网络的API。(港口码头/车站)Socket是应用层与TCP/IP协议族通信的中间软件抽象层,它是一组接口。socket是在应用层和传输层之间的一个抽象层,它把TCP/IP层复杂的操作抽象为几个简单的接口供应用层调用已实现进程在网络中通信。

Websocket:同HTTP一样也是应用层的协议,但是它是一种双向通信协议,是建立在TCP之上的,解决了服务器与客户端全双工通信的问题,包含两部分:一部分是“握手”,一部分是“数据传输”。握手成功后,数据就直接从 TCP 通道传输,与 HTTP 无关了。

*注:什么是单工、半双工、全工通信?
数据只能单向传送为单工;
数据能双向传送但不能同时双向传送称为半双工;
数据能够同时双向传送则称为全双工。

TCP/UDP区别:
TCP(传输控制协议,Transmission Control Protocol):(类似打电话)
面向连接、传输可靠(保证数据正确性)、有序(保证数据顺序)、传输大量数据(流模式)、速度慢、对系统资源的要求多,程序结构较复杂,
每一条TCP连接只能是点到点的,
TCP首部开销20字节。
UDP(用户数据报协议,User Data Protocol):(类似发短信)
面向非连接 、传输不可靠(可能丢包)、无序、传输少量数据(数据报模式)、速度快,对系统资源的要求少,程序结构较简单 ,
UDP支持一对一,一对多,多对一和多对多的交互通信,
UDP的首部开销小,只有8个字节。简化的TCP/IP四层模型主要分为:应用层、传输层、网络层、数据链路层。

3.WebSocket和Http的关系和异同点

每个WebSocket连接都始于一个HTTP请求。 具体来说,WebSocket协议在第一次握手连接时,通过HTTP协议在传送WebSocket支持的版本号,协议的字版本号,原始地址,主机地址等等一些列字段给服务器端.

Upgrade首部,用来把当前的HTTP请求升级到WebSocket协议,这是HTTP协议本身的内容,是为了扩展支持其他的通讯协议。 如果服务器支持新的协议,则必须返回101.

一个WebSocket连接是在客户端与服务器之间HTTP协议的初始握手阶段将其升级到Web Socket协议来建立的,其底层仍是TCP/IP连接

相同点:
(1)都是建立在TCP之上,通过TCP协议来传输数据。
(2)都是可靠性传输协议。
(3)都是应用层协议。

不同点:
(1)WebSocket支持持久连接,HTTP不支持持久连接。

(2)WebSocket是双向通信协议,HTTP是单向协议,只能由客户端发起,做不到服务器主动向客户端推送信息。

4.那么为什么说http协议并不是一个持久连接的协议呢?

(1)Http的生命周期通过Request来界定,也就是Request一个Response,那么在Http1.0协议中,这次Http请求就结束了。在Http1.1中进行了改进,是的有一个Keep-alive,也就是说,在一个Http连接中,可以发送多个Request,接收多个Response。但是必须记住,在Http中一个Request只能对应有一个Response,而且这个Response是被动的,不能主动发起。

(2)WebSocket是基于Http协议的,或者说借用了Http协议来完成一部分握手,在握手阶段与Http是相同的。

5.WebSocket可以穿越防火墙吗?

WebSocket使用标准的80及443端口,这两个都是防火墙友好协议,Web Sockets使用HTTP Upgrade机制升级到Web Socket协议。HTML5 Web Sockets有着兼容HTTP的握手机制,因此HTTP服务器可以与WebSocket服务器共享默认的HTTP与HTTPS端(80和443)。

6.WebSocket和Socket

Socket 其实并不是一个协议,而是为了方便使用 TCP 或 UDP 而抽象出来的一层,是位于应用层和传输控制层之间的一组接口。 Socket本身并不是一个协议,它工作在OSI模型会话层,是一个套接字,TCP/IP网络的API,是为了方便大家直接使用。

更底层协议而存在的一个抽象层。Socket其实就是一个门面模式,它把复杂的TCP/IP协议族隐藏在Socket接口后面,对用户来说,一组简单的接口就是全部,让Socket去组织数据,以符合指定的协议。
而WebSocket则是一个典型的应用层协议。

7.WebSocket  HTTP和TCP/IP

WebSocket和HTTP一样,都是建立在TCP之上,通过TCP来传输数据。

8.Socket和TCP/IP
  Socket是对TCP/IP协议的封装,像创建Socket连接时,可以指定使用的传输层协议,Socket可以支持不同的传输层协议(TCP或UDP),当使用TCP协议进行连接时,该Socket连接就是一个TCP连接。

36、redis数据类型分别使用的场景

String——字符串
字符串类型是Redis最基础的数据结构,字符串类型可以是JSON、XML甚至是二进制的图片等数据,但是最大值不能超过512MB。

场景:缓存、计数(文章的阅读量,视频的播放量)、共享session、限速(同一个IP同一段时间只能访问不能超过N次)
● Hash——字典
Redis中,哈希类型是指一个键值对的存储结构。

哈希类型的内部编码有两种:

ziplist(压缩列表):当哈希类型元素个数小于hash-max-ziplist-entries配置(默认512个)同时所有值都小于hash-max-ziplist-value配置(默认64字节)时使用。ziplist使用更加紧凑的结构实现多个元素的连续存储,所以比hashtable更加节省内存。hashtable(哈希表):当ziplist不能满足要求时,会使用hashtable

使用场景:数据库有用户表结构,以id为key,其他字段为value存储。使用哈希存储会比字符串更加方便直观

● List——列表
列表类型用来存储多个有序的字符串,一个列表最多可以存储2^32-1个元素,列表的两端都可以插入和弹出元素。(redis使用双端链表实现的List)

使用场景:消息队列(有序)、栈(先进后出)、文章列表(使用命令lrange key 0 9分页获取文章列表)
● Set——集合

集合类型也可以保存多个字符串元素,与列表不同的是,集合中不允许有重复元素并且集合中的元素是无序的。一个集合最多可以存储2^32-1个元素。
使用场景:用户标签、抽奖功能(分别抽一等奖、二等奖)
● Sorted Set——有序集合

有序集合和集合一样,不能有重复元素。但是可以排序,它给每个元素设置一个score作为排序的依据。最多可以存储2^32-1个元素。

使用场景:排行榜、延迟消息队列(下单系统,下单后需要在15分钟内进行支付,如果15分钟未支付则自动取消订单。将下单后的十五分钟后时间作为score,订单作为value存入redis,消费者轮询去消费,如果消费的大于等于这笔记录的score,则将这笔记录移除队列,取消订单)

37、Redis的持久化是什么

RDB持久化:该机制可以在指定的时间间隔内生成数据集的时间点快照(point-in-time snapshot)。
AOF持久化:记录服务器执行的所有写操作命令,并在服务器启动时,通过重新执行这些命令来还原数
据集。AOF文件中的命令全部以Redis协议的格式来保存,新命令会被追加到文件的末尾。Redis还可以
在后台对AOF文件进行重写(rewrite),使得AOF文件的体积不会超出保存数据集状态所需的实际大
小。

img
img

网上学习资料一大堆,但如果学到的知识不成体系,遇到问题时只是浅尝辄止,不再深入研究,那么很难做到真正的技术提升。

需要这份系统化的资料的朋友,可以添加戳这里获取

一个人可以走的很快,但一群人才能走的更远!不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!

AOF持久化:记录服务器执行的所有写操作命令,并在服务器启动时,通过重新执行这些命令来还原数
据集。AOF文件中的命令全部以Redis协议的格式来保存,新命令会被追加到文件的末尾。Redis还可以
在后台对AOF文件进行重写(rewrite),使得AOF文件的体积不会超出保存数据集状态所需的实际大
小。

[外链图片转存中…(img-Zv39AKMr-1715649469871)]
[外链图片转存中…(img-iJC6qC00-1715649469871)]

网上学习资料一大堆,但如果学到的知识不成体系,遇到问题时只是浅尝辄止,不再深入研究,那么很难做到真正的技术提升。

需要这份系统化的资料的朋友,可以添加戳这里获取

一个人可以走的很快,但一群人才能走的更远!不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!

  • 19
    点赞
  • 26
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值