golang面试题004

victorfengming · · 961 次点击 · · 开始浏览    
这是一个创建于 的文章,其中的信息可能已经有所发展或是发生改变。

# Golang并发编程 ## 1. Mutex几种状态 1、[Mutex](https://studygolang.com/articles/19370) 几种状态 ⚫ mutexLocked — 表示互斥锁的锁定状态; ⚫ mutexWoken — 表示从正常模式被从唤醒; ⚫ mutexStarving — 当前的互斥锁进入饥饿状态; ⚫ waitersCount — 当前互斥锁上等待的 Goroutine 个数; ## 2. Mutex正常模式和饥饿模式 **正常模式(非公平锁)** >正常模式下,所有等待锁的 goroutine 按照 FIFO(先进先出)顺序等待。唤醒的 goroutine 不会直接拥有锁,而是会和新请求锁的 goroutine 竞争锁的拥有。 >新请求锁的 goroutine 具有优势:它正在 CPU 上执行,而且可能有好几个,所 以刚刚唤醒的 goroutine 有很大可能在锁竞争中失败。 >在这种情况下,这个被唤醒的 goroutine 会加入到等待队列的前面。 > 如果一个等待的 goroutine 超过1ms 没有获取锁,那么它将会把锁转变为饥饿模式。 **饥饿模式(公平锁)** >为了解决了等待 G 队列的长尾问题 饥饿模式下,直接由 unlock 把锁交给等待队列中排在第一位的 G(队头),同 时,饥饿模式下,新进来的 G 不会参与抢锁也不会进入自旋状态,会直接进入 等待队列的尾部,这样很好的解决了老的 g 一直抢不到锁的场景。 > 饥饿模式的触发条件,当一个 G 等待锁时间超过 1 毫秒时,或者当前队列只剩 下一个 g 的时候,Mutex 切换到饥饿模式。 **总结** >对于两种模式,正常模式下的性能是最好的,goroutine 可以连续多次获取 锁,饥饿模式解决了取锁公平的问题,但是性能会下降,其实是性能和公平的 一个平衡模式。 > > # 3、Mutex 允许自旋的条件 1. 锁已被占用,并且锁不处于饥饿模式。 2. 积累的自旋次数小于最大自旋次数(active_spin=4)。 3. cpu 核数大于 1。 4. 有空闲的 P。 5. 当前 goroutine 所挂载的 P 下,本地待运行队列为空。 # 4,RWMutex实现 >通过记录 `readerCount`读锁的数量进行控制,当有一个写锁的时候,会将读锁数量设置为负数 `1<<30`. 目的是让新进入的读锁等待写锁之后释放通知 读锁. >同样的写锁也会等等待之前的读锁都释放完毕,才会开始进行后续的操作. > 而等写锁释放完之后,会将值重新加上`1<<30`,并通知刚才新进入的读锁 > `(rw.readerSem)`两者互相限制 > # 5, RWMutex 注意事项 - [studygoalng-golang读写锁RWMutex](https://studygolang.com/articles/2497) ⚫ RWMutex 是单写多读锁,该锁可以加多个读锁或者一个写锁 ⚫ 读锁占用的情况下会阻止写,不会阻止读,多个 goroutine 可以同时获取读锁 ⚫ 写锁会阻止其他 goroutine(无论读和写)进来,整个锁由该 goroutine独占 ⚫ 适用于读多写少的场景 ⚫ RWMutex 类型变量的零值是一个未锁定状态的互斥锁。 ⚫ RWMutex 在首次被使用之后就不能再被拷贝。 ⚫ RWMutex 的读锁或写锁在未锁定状态,解锁操作都会引发 panic。 ⚫ RWMutex 的一个写锁 Lock 去锁定临界区的共享资源,如果临界区的共享资源已被(读锁或写锁)锁定,这个写锁操作的 goroutine 将被阻塞直到解锁。 ⚫ RWMutex 的读锁不要用于递归调用,比较容易产生死锁。 ⚫ RWMutex 的锁定状态与特定的 goroutine 没有关联。一个goroutine可以RLock(Lock),另一个goroutine可以RUnlock(Unlock)。 ⚫ 写锁被解锁后,所有因操作锁定读锁而被阻塞的 goroutine 会被唤醒,并都可以成功锁定读锁。 ⚫ 读锁被解锁后,在没有被其他读锁锁定的前提下,所有因操作锁定写锁而被阻塞的 goroutine,其中等待时间最长的一个 goroutine 会被唤醒 ## 6、Cond 是什么 Cond 实现了一种条件变量,可以使用在多个 Reader 等待共享资源 ready 的场 景(如果只有一读一写,一个锁或者 channel 就搞定了) 每个 Cond 都会关联一个 Lock(*sync.Mutex or *sync.RWMutex),当修改条 件或者调用 Wait 方法时,必须加锁,保护 condition ## 7、Broadcast 和 Signal 区别 ```go func (c *Cond) Broadcast() //Broadcast 会唤醒所有等待 c 的 goroutine。 //调用 Broadcast 的时候,可以加锁,也可以不加锁。 func (c *Cond) Signal() //Signal 只唤醒 1 个等待 c 的 goroutine。 //调用 Signal 的时候,可以加锁,也可以不加锁。 ``` ## 8、Cond 中 Wait 使用 ```go func (c *Cond) Wait() Wait()会自动释放 c.L,并挂起调用者的 goroutine。之后恢复执行,Wait()会 在返回时对 c.L 加锁。 除非被 Signal 或者 Broadcast 唤醒,否则 Wait()不会返回。 由于 Wait()第一次恢复时,C.L 并没有加锁,所以当 Wait 返回时,调用者通常 并不能假设条件为真。 取而代之的是, 调用者应该在循环中调用 Wait。(简单来说,只要想使用 condition,就必须加锁。) ``` ```go c.L.Lock() for !condition() { c.Wait() } ... make use of condition ... c.L.Unlock() ``` ## 9、WaitGroup 用法 一个 WaitGroup 对象可以等待一组协程结束。使用方法是: 1. main 协程通过调用 wg.Add(delta int) 设置 worker 协程的个数,然后创建 worker 协程; 2. worker 协程执行结束以后,都要调用 wg.Done(); 3. main 协程调用 wg.Wait() 且被 block,直到所有 worker 协程全部执行结束后返回。 ## 10、WaitGroup 实现原理 ⚫ WaitGroup 主要维护了 2 个计数器,一个是请求计数器 v,一个是等待计数 器 w,二者组成一个 64bit 的值,请求计数器占高 32bit,等待计数器占低 32bit。 ⚫ 每次 Add 执行,请求计数器 v 加 1,Done 方法执行,请求计数器减 1,v 为 0 时通过信号量唤醒 Wait()。 # Golang并发编程 ## 11 什么是 sync.Once ⚫ Once 可以用来执行且仅仅执行一次动作,常常用于单例对象的初始化场 景。 ⚫ Once 常常用来初始化单例资源,或者并发访问只需初始化一次的共享资 源,或者在测试的时候初始化一次测试资源。 ⚫ sync.Once 只暴露了一个方法 Do,你可以多次调用 Do 方法,但是只有第 一次调用 Do 方法时 f 参数才会执行,这里的 f 是一个无参数无返回值 的函数。 ## 12、什么操作叫做原子操作 一个或者多个操作在 CPU 执行过程中不被中断的特性,称为原子性 (atomicity)。这些操作对外表现成一个不可分割的整体,他们要么都执行,要 么都不执行,外界不会看到他们只执行到一半的状态。而在现实世界中,CPU 不可能不中断的执行一系列操作,但如果我们在执行多个操作时,能让他们的 中间状态对外不可见,那我们就可以宣城他们拥有了“不可分割”的原子性。** ** 在 Go 中,一条普通的赋值语句其实不是一个原子操作。列如,在 32 位机器上 写 int64 类型的变量就会有中间状态,因为他会被拆成两次写操作(MOV)——写 低 32 位和写高 32 位。 ## 13、原子操作和锁的区别 原子操作由底层硬件支持,而锁则由操作系统的调度器实现。锁应当用来保护 一段逻辑,对于一个变量更新的保护,原子操作通常会更有效率,并且更能利 用计算机多核的优势,如果要更新的是一个复合对象,则应当使用 atomic.Value 封装好的实现。 ## 14、什么是 CAS CAS 的全称为 Compare And Swap,直译就是比较交换。是一条 CPU 的原子指 令,其作用是让 CPU 先进行比较两个值是否相等,然后原子地更新某个位置的 值,其实现方式是给予硬件平台的汇编指令,在 intel 的 CPU 中,使用的 cmpxchg 指令,就是说 CAS 是靠硬件实现的,从而在硬件层面提升效率。 简述过程是这样: 假设包含 3 个参数内存位置(V)、预期原值(A)和新值(B)。V 表示要更新变量的 值,E 表示预期值,N 表示新值。仅当 V 值等于 E 值时,才会将 V 的值设为 N, 如果 V 值和 E 值不同,则说明已经有其他线程在做更新,则当前线程什么都不 做,最后 CAS 返回当前 V 的真实值。CAS 操作时抱着乐观的态度进行的,它总 是认为自己可以成功完成操作。基于这样的原理,CAS 操作即使没有锁,也可 以发现其他线程对于当前线程的干扰。 ## 15,sync.Pool有什么用 对于很多需要重复分配,回收内存的地方,sync.Pool是一个很好的选择. 频繁地分配内存会给GC带来一定的负担,严重的时候会引起CPU的毛刺 而sync.Pool可以将暂时不用的对象缓存起来,待下次需要的时候直接使用 ,不用再次经过内存分配,复用对象的内存,减GC压力,提升系统性能

有疑问加站长微信联系(非本文作者))

入群交流(和以上内容无关):加入Go大咖交流群,或添加微信:liuxiaoyan-s 备注:入群;或加QQ群:692541889

961 次点击  
加入收藏 微博
上一篇:golang面试题003
下一篇:golang面试题005
暂无回复
添加一条新回复 (您需要 登录 后才能回复 没有账号 ?)
  • 请尽量让自己的回复能够对别人有帮助
  • 支持 Markdown 格式, **粗体**、~~删除线~~、`单行代码`
  • 支持 @ 本站用户;支持表情(输入 : 提示),见 Emoji cheat sheet
  • 图片支持拖拽、截图粘贴等方式上传